/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Query Module:	Gimme's CSS Querying Engine
*
*	Requires:
*	Nothing
*
*/

var Gimme = {};
(function()
{
	// the querying engine
	var Engine = new function()
	{
		// private variables
		var nthHashHash = {};
		var DOMBool = new function()
		{
			this.val = -2;
			this.t = 0;
			this.f = 0;
			this.inc = function()
			{
				this.val += 2;
				this.t = this.val + 1;
				this.f = this.val;
			};
		};

		// public methods
		this.query = query;
		this.processSelector = processSelector;
		this.nthCacheContains = nthCacheContains;

		// member functions
		function query(_s, _node)
		{
	
			// If the passed in selector is "false-like" return an empty array
			if (!_s)
			{
				return [];
			}
			// If the passed in selector /is/ an array, just give it back
			if (_s instanceof Array)
			{
				return _s;
			}
			// Finally, if it's anything other than a string, return it as the sole element in an array (generally, I expect this to be a DOM element)
			if (typeof _s !== 'string')
			{
				return [ _s ];
			}
			
			if (typeof document.querySelectorAll !== 'undefined')
			{
				try
				{
					return makeArray(document.querySelectorAll(_s));
				}
				catch (everything)
				{
					//alert(everything.message);
				}
			}			

			// Reset the nthHashHash
			nthHashHash = {};

			// Invalidate the DOMBool (expando properties that may have been added to some DOM elements)
			DOMBool.inc();

			// Parse the selector, supply a _node (usually the acting root node, but could also be the lhs of a partial selector), and go fetch some elements!
			return select(CSSParser.parseSelector(_s), _node);
		}

		function select(_o, _node)
		{
			var s = _o.selectors
			,hints = _o.hints
			,anchor = hints.anchor;
			_node = _node || document.documentElement;

			// if the anchor's .elem property is null, it means a needed ID in the selector was not found in the DOM, therefore failure is imminent
			if (anchor.elem === null)
			{
				return [];
			}

			if (hints.isPartialQuery)
			{
				s.unshift(CSSParser.createReferenceSelector(_node));
				_node = _node.parentNode;
			}
			else if (anchor.elem !== -1 && s.length > 1)
			{
				// if the anchor's .elem property is /not/ -1, it means that a useful "anchor" was found which can be used to (potentially) speed up the query.
				// note however, that we only care about the anchor element for queries that contains at least one combinator (i.e. selectorParts.length > 1)
				_node = anchor.elem;
			}

			// the parser may have been able to retrieve an initial collection of elements, in which case we should use it (this is mostly to speed up direct-child (>) queries)
			if (hints.initialCollection !== null)
			{
				elems = filterElements(hints.initialCollection, s[s.length - 1]);
			}
			// otherwise, execute a simpleGimme to get intial collection of elems to be filtered
			else
			{
				elems = simpleGimme(s[s.length - 1], _node);
			}

			if (s.length === 1)
			{
				return elems;
			}

			if (s.length === 3)
			{
				if (anchor.isIdeal)
				{
					return elems;
				}
				else
				{
					// TODO:  deal with non 3-piece ideal anchor here
				}
			}

			var counter = 0;
			var i, j, len = elems.length;
			var combinatorFunctions = selectorRules.combinator;
			var lhs, rhs, cursor, combinator, previousCombinator = null;
			var matched = [];

			matchSeeker:
			for (i = 0; i < len; i++)
			{
				j = s.length - 2;
				combinator = s[j];
				cursor = rhs = elems[i];

				while (combinator)
				{
					lhs = s[j - 1];

					if (!combinatorFunctions[combinator](lhs, cursor))
					{
						// If previousCombinator was descendant while the current combinator is not (descendant), it means that failure of the combinatorFunction is not conclusive.
						// Therefore, we need to cycle the cursor back to its .parentNode and continue the while loop without advancing any further through the selectors array, s
						// Note however, that we only want to do this in the event that the cursor's .parentNode isn't null (meaning that we've hit the top of the document)
						if (cursor.parentNode && previousCombinator === ' ' && combinator !== ' ')
						{
							cursor = cursor.parentNode;
							continue;
						}
						else
						{
							continue matchSeeker;
						}
					}

					cursor = lhs.cursor;
					j -= 2;
					previousCombinator = combinator;
					combinator = j < 1 ? null : s[j];
				}

				matched[counter++] = rhs;
			}

			return matched;
		}

		function processSelector(_ss, _elem)
		{
			if (!_elem)
			{
				return false;
			}
			if (_ss.refersTo)
			{
				return _ss.refersTo === _elem;
			}

			var inProps = _ss.inProps
			,id = inProps.id
			,tag = inProps.tag
			,classes = inProps.classes
			,attributes = inProps.attributes
			,pseudos = inProps.pseudos;

			var inRules = selectorRules.inRules;
			var exRules = selectorRules.exRules;

			if (id && _elem.id !== id
				|| tag && _elem.tagName !== tag
				|| classes.length > 0 && !inRules['.'].processFn(_elem, classes)
				|| attributes.length > 0 && !inRules['['].processFn(_elem, attributes)
				|| pseudos.length > 0 && !inRules[':'].processFn(_elem, pseudos))
			{
				return false;
			}

			var key, ruleName, exProps = _ss.exProps;
			for (key in exRules)
			{
				ruleName = exRules[key].name;
				if (exProps[ruleName] && !exRules[key].processFn(_elem, exProps[ruleName]))
				{
					return false;
				}
			}

			return true;
		}

		function simpleGimme(_ss, _root)
		{
			_root = _root || document.documentElement;
			var counter = 0;
			var i, len;
			var elems, inProps = _ss.inProps, exProps = _ss.exProps;
			if (inProps.id)
			{
				elems = [ document.getElementById(inProps.id) ];
				delete inProps.id;
			}
			else
			{
				// TODO:  this should be anywhere in the pseudos, not just the last part -- must fix
				var p = inProps.pseudos[inProps.pseudos.length - 1];
				if (p && p.name === 'nth-child')
				{
					elems = buildNthArray(p.param.a, p.param.b, inProps.tag);
					inProps.pseudos = inProps.pseudos.splice(inProps.pseudos.length, 1);
				}
				else
				{
					elems = _root.getElementsByTagName(inProps.tag || '*');
				}

				if (exProps.isEmpty
					&& inProps.classes.length < 1
					&& inProps.attributes.length < 1
					&& inProps.pseudos.length < 1)
				{
					return makeArray(elems);
				}
				delete inProps.tag;
			}

			var matched = [];
			len = elems.length;
			for (i = 0; i < len; i++)
			{
				if (Engine.processSelector(_ss, elems[i]))
				{
					matched[counter++] = elems[i];
				}
			}

			return matched;
		}

		function filterElements(_elems, _ss)
		{
			var matched = []
			,counter = 0
			,len = _elems.length
			,elem
			,i;

			for (i = 0; i < len; i++)
			{
				elem = _elems[i];
				if (elem.nodeType !== 1)
				{
					continue;
				}

				if (processSelector(_ss, elem))
				{
					matched[counter++] = elem;
				}
			}

			return matched;
		}

		function makeArray(_enumerable)
		{
			var matched = []
			,counter = 0
			,len = _enumerable.length
			,i;

			for (i = 0; i < len; i++)
			{
				matched[counter++] = _enumerable[i];
			}
			return matched;
		}

		function buildNthArray(_a, _b, _tag)
		{
			return nthHashHash[_a + 'n+' + _b] = nth(_a, _b, _tag, true);
		}

		function buildNthHash(_a, _b)
		{
			return nthHashHash[_a + 'n+' + _b] = nth(_a, _b, null, false);
		}

		function nth(_a, _b, _tag, _includeElement)
		{
			if (_tag === '*')
			{
				_tag = null;
			}

			var all = document.getElementsByTagName('*')
			,len = all.length
			,sibHash = {}
			,count = 0
			,uid
			,elem
			,i
			,x;

			var matched = _includeElement ? [] : {};
			for (i = 0; i < len; i++)
			{
				elem = all[i];
				uid = getObjectGUID(elem.parentNode);
				sibHash[uid] = sibHash[uid] || 0;									// sibHash[uid] keeps count of siblings... if uninitialized, it will be given a value of 0
				x = (count = ++sibHash[uid]) - _b;									// FIRST, count and sibHash[uid] both get incremented, and THEN, x = count - b

				if (_a === 0 && (_tag && elem.tagName !== _tag ? false : true))
				{
					if (x === 0)
					{
						if (_includeElement)
						{
							matched.push(elem);
						}
						matched[getObjectGUID(elem)] = true;
					}
				}
				else if ((_a * x >= 0) && (x % _a === 0) && (_tag && elem.tagName !== _tag ? false : true))
				{
					if (_includeElement)
					{
						matched.push(elem);
					}
					matched[getObjectGUID(elem)] = true;
				}
			}

			return matched;
		}

		function nthCacheContains(_a, _b, _elemGUID)
		{
			var nthKey = _a + 'n+' + _b;
			var h = nthHashHash[nthKey];
			if (!h)
			{
				h = nthHashHash[nthKey] = buildNthHash(_a, _b);
			}
			return h[_elemGUID] === true;
		}
	};

	
	// the css parser
	var CSSParser = new function()
	{
		var rules = null
		,normalizingPattern = null
		,combinators = '';

		this.setRules = function(_rules)
		{
			rules = _rules;

			var symbol;
			for (symbol in rules.combinator)
			{
				if (rules.combinator.hasOwnProperty(symbol))
				{
					combinators += symbol;
				}
			}
			normalizingPattern = new RegExp('\\s*([' + combinators + '])\\s*', 'g');
		};

		this.parseSelector = function(_s)
		{
			// Trim beginning and ending white space, and remove space from around combinators
			_s = trim(_s).replace(normalizingPattern, '$1');

			var rule = null
			,selectors = []
			,simpleSelector = new SimpleSelector();

			var hints = { anchor: new SelectorAnchor(), isPartialQuery: false, initialCollection: null }
			,currentAnchor = hints.anchor
			,previousAnchor = currentAnchor
			,anchorElem;

			var startSkip
			,endSkip
			,startModifier
			,endModifier
			,endsWith;

			var c, i = 0, part = '', len = _s.length;
			while (i < len)
			{
				c = _s.charAt(i);
				rule = rule || selectorRules.inRules[c] || selectorRules.exRules[c];
				if (rule)
				{
					endsWith = rule.endsWith + (rule.stopForCombinators === false ? '' : combinators);
					startSkip = rule.startSkip || 0;
					endSkip = rule.endSkip || 0;
					startModifier = rule.startModifier || null;
					endModifier = rule.endModifier || null;

					c = _s.charAt(i += startSkip);
					while (endsWith.indexOf(c) === -1)
					{
						if (c === startModifier)
						{
							while (c && c !== endModifier)
							{
								part += c;
								c = _s.charAt(++i);
							}
						}
						part += c;
						c = _s.charAt(++i);
					}

					if ((anchorElem = simpleSelector.addPart(part, rule)) !== -1)
					{
						previousAnchor = currentAnchor;
						currentAnchor = new SelectorAnchor(selectors.length, anchorElem);
					}

					i += endSkip;
					part = '';
					rule = null;
					continue;
				}
				else
				{
					if (combinators.indexOf(c) !== -1)
					{
						// An anchor that immediately preceeds a sibling-type combinator (+ or ~) is problematic and needs to be reverted (currentAnchor = previousAnchor).
						// If the combinator, c, was /not/ sibling-type, "lock in" the currentAnchor (previousAnchor = currentAnchor).
						(c === '+' || c === '~') ? currentAnchor = previousAnchor : previousAnchor = currentAnchor;

						if (!(hints.isPartialQuery = i === 0))
						{
							selectors.push(simpleSelector);
							simpleSelector = new SimpleSelector();
						}
						selectors.push(c);

						// HACK:  this is a total hack, but it yields a speed boost on certain direct-child queries
						hints.initialCollection = (c === '>' && currentAnchor.elem && currentAnchor.elem !== -1 && currentAnchor.index === selectors.length - 2) ? currentAnchor.elem.childNodes : null;
					}
					else
					{
						rule = selectorRules.inRules['tag'];
						continue;
					}
				}

				i++;
			}

			selectors.push(simpleSelector);
			currentAnchor.isIdeal = currentAnchor.elem !== -1 && selectors.length - 3 === currentAnchor.index;
			hints.anchor = currentAnchor;
			return { selectors: selectors, hints: hints };
		};

		this.parseAttribute = function(_attr)
		{
			var pattern = /\=|\^=|\$=|\*=|\|=|~=|!=/;
			var delim = _attr.match(pattern);
			delim = delim && delim[0];
			var avPair = _attr.split(delim);
			var attr = avPair[0] && trim(avPair[0].replace(/[\[\]"]/g, ''));
			var val = avPair[1] && trim(avPair[1].replace(/[\[\]"]/g, ''));
			return { name: attr, delim: delim, val: val };
		};

		this.parsePseudo = function(_pseudo)
		{
			var pattern = /(.*)\((.*)\)/;
			var m = _pseudo.match(pattern);
			var name = (m && m[1]) || _pseudo;
			var param = m && m[2];
			var a, b, n;
			if (name === 'nth-child')
			{
				if (param === 'even')
				{
					param = { wholeValue: param, a: 2, b: 0 };
				}
				else if (param === 'odd')
				{
					param = { wholeValue: param, a: 2, b: 1 };
				}
				else
				{
					// valid patterns are in the form an+b,
					// where a is 1 if unspecified
					// where b is 0 if unspecified
					// where n is 0 if unspecified
					// a and b can be either positive or negative (specifying + for positive is optional)
					pattern = /([+-]?\d+)?(n)?([+-]?\d+)?/;

					// match it
					m = param.match(pattern);

					a = parseInt(m[1]) || 1;                  // if a is unspecified, default to 1
					n = m[2] ? 1 : 0;	                      // if n is absent from the string, default it to 0
					b = a && !n ? a : parseInt(m[3]) || 0;    // if b is unspecified, default to 0

					param = { wholeValue: param, a: a * n, b: b };
				}
			}
			return { name: name, param: param };
		};

		this.createReferenceSelector = function(_ref)
		{
			return new SimpleSelector(_ref);
		};

		// SimpleSelector Class
		function SimpleSelector(_ref)
		{
			this.cursor = null;
			this.refersTo = _ref;
			this.inProps =
			{
				id: null
				,tag: null
				,classes: []
				,attributes: []
				,pseudos: []
			};

			this.exProps =
			{
				isEmpty: true
			};

			var key, rule, exRules = selectorRules.exRules;
			for (key in exRules)
			{
				if (exRules.hasOwnProperty(key))
				{
					delete this.exProps.isEmpty;
					rule = exRules[key];
					switch (rule.objType)
					{
						case 'array':
							this.exProps[rule.name] = [];
							break;

						case 'null':
						default:
							this.exProps[rule.name] = null;
							break;
					}
				}
			}
		}
		SimpleSelector.prototype.addPart = function(_part, _rule)
		{
			var name = _rule.name
			,preFn = _rule.preFn
			,hintFn = _rule.hintFn
			,hint = -1;

			if (typeof preFn === 'function')
			{
				_part = preFn(_part);
			}

			var thing = typeof this.inProps[name] !== 'undefined' ? this.inProps : this.exProps;
			thing[name] instanceof Array ? thing[name].push(_part) : thing[name] = _part;

			if (typeof hintFn === 'function')
			{
				hint = hintFn(_part);
			}

			return hint;
		};
		// End SimpleSelector Class

		// SelectorAnchor Class
		function SelectorAnchor(_idx, _elem, _isIdeal)
		{
			this.index = _idx === 0 ? 0 : _idx || NaN;
			this.elem = _elem  === null ? null : (_elem || -1);
			this.isIdeal = _isIdeal || false;
		}
		// End SelectorAnchor Class
	};


	// internal rules for selectors and combinators
	var selectorRules =
	{
		inRules:
		{
			'tag':
			{
				name: 'tag'
				,endsWith: '#.[:'
				,preFn: function(_part) { return _part.toUpperCase(); }
				,hintFn: function(_part) { return _part === 'BODY' || _part === 'HTML' ? document.getElementsByTagName(_part)[0] : -1; }
				,processFn: function(_elem, _tag)
				{
					return _elem.tagName === _tag;
				}
			}

			,'#':
			{
				name: 'id'
				,endsWith: '#.[:'
				,startSkip: 1
				,preFn: null
				,hintFn: function(_part)
				{
					return document.getElementById(_part);
				}
				,processFn: function(_elem, _id)
				{
					return _elem.id === _id;
				}
			}

			,'.':
			{
				name: 'classes'
				,endsWith: '.[:'
				,startSkip: 1
				,objType: 'array'
				,processFn: function(_elem, _classes)
				{
					var className = _elem.className;
					if (typeof className !== 'string')
					{
						className = _elem.getAttribute('class');
					}
	                
					if (!className || className.length < 1)
					{
						return false;
					}

					var elemClass = ' ' + className + ' ';
					var i, len = _classes.length;
					for (i = 0; i < len; i++)
					{
						if (elemClass.indexOf(' ' + _classes[i] + ' ') === -1)
						{
							return false;
						}
					}
	                return true;
				}
			}

			,'[':
			{
				name: 'attributes'
				,endsWith: ']'
				,stopForCombinators: false
				,startSkip: 1
				,endSkip: 1
				,startModifier: '"'
				,endModifier: '"'
				,objType: 'array'
				,preFn: CSSParser.parseAttribute
				,processFn: function(_elem, _attributes)
				{
					var i, len = _attributes.length;
					var attr, attrName, attrVal, attrDelim, elemAttrVal;
					for (i = 0; i < len; i++)
					{
						attr = _attributes[i];
						attrName = attr.name;
						attrVal = attr.val;
						attrDelim = attr.delim;

						elemAttrVal = readAttribute(_elem, attrName);
						if (elemAttrVal === null)
						{
							return false;
						}
						else if (!attrDelim)
						{
							continue;
						}
						else if (!selectorRules.attr[attrDelim || 'unknown'](elemAttrVal, attrVal))
						{
							return false;
						}
					}
					return true;
				}
			}

			,':':
			{
				name: 'pseudos'
				,endsWith: '#.[:)'
				,startSkip: 1
				,startModifier: '('
				,endModifier: ')'
				,objType: 'array'
				,preFn: CSSParser.parsePseudo
				,processFn: function(_elem, _pseudos)
				{
					var pseudo
					,fn
					,len = _pseudos.length
					,i;

					for (i = 0; i < len; i++)
					{
						pseudo = _pseudos[i];
						fn = selectorRules.pseudo[pseudo.name] || selectorRules.pseudo.unknown;
						if (!fn(_elem, pseudo))
						{
							return false;
						}
					}
					return true;
				}
			}
		}

		,exRules:
		{
		}

		,attr:
		{
			'=': function(_attr, _val)
			{
				return _attr === _val;
			}

			,'^=': function(_attr, _val)
			{
				return _attr.indexOf(_val) === 0
			}

			,'$=': function(_attr, _val)
			{
				var lio = _attr.lastIndexOf(_val);
				return lio !== -1 && lio + _val.length === _attr.length;
			}

			,'*=': function(_attr, _val)
			{
				return _attr.indexOf(_val) !== -1;
			}

			,'|=': function(_attr, _val)
			{
				return (_attr === _val) || (_attr.indexOf(_val + '-') === 0);
			}

			,'~=': function(_attr, _val)
			{
				return (_attr === _val) || contains(_attr.split(' '), _val);
			}

			,'!=': function(_attr, _val)
			{
				return _attr !== _val;
			}

			,'unknown': function()
			{
				return false;
			}
		}

		,pseudo:
		{
			'first-child': child('previous')
			,'last-child': child('next')
			,'only-child': function(_elem)
			{
				return selectorRules.pseudo['first-child'](_elem) && selectorRules.pseudo['last-child'](_elem);
			}

			,'nth-child': function(_elem, _pseudo)
			{
				var a = _pseudo.param.a;
				var b = _pseudo.param.b;

				// check the an+b cache for _elem
				return Engine.nthCacheContains(a, b, getObjectGUID(_elem));
			}

			,'contains': function(_elem, _pseudo)
			{
				return _elem.innerHTML.indexOf(_pseudo.param) !== -1;
			}

			,'unknown': function()
			{
				return false;
			}
		}

		,combinator:
		{
			' ': function (_lhs, _rhs)
			{
				var p = _rhs.parentNode;
				while (p && p !== document)
				{
					if (Engine.processSelector(_lhs, p))
					{
						_lhs.cursor = p;
						return true;
					}
					p = p.parentNode;
				}

				return false;
			}

			,'>': function(_lhs, _rhs)
			{
				var p = _rhs.parentNode;
				_lhs.cursor = p;
				return Engine.processSelector(_lhs, p);
			}
			
			,'<': function(_lhs, _rhs)
			{
				var children = _rhs.childNodes;
				var i, len = children.length;
				for (i = 0; i < len; i++)
				{
					if (Engine.processSelector(_lhs, children[i]))
					{
						return true;
					}
				}
				return false;
			}

			,'~': function(_lhs, _rhs)
			{
				var prevSib = _rhs.previousSibling;
				while (prevSib)
				{
					if (prevSib.nodeType === 1 && Engine.processSelector(_lhs, prevSib))
					{
						_lhs.cursor = prevSib;
						return true;
					}
					else
					{
						prevSib = prevSib.previousSibling;
					}
				}
				return false;
			}

			,'+': function(_lhs, _rhs)
			{
				var prevSib = _rhs.previousSibling;
				while (prevSib && prevSib.nodeType !== 1)
				{
					prevSib = prevSib.previousSibling;
				}
				_lhs.cursor = prevSib;
				return Engine.processSelector(_lhs, prevSib);
			}

			,',': function()
			{
				return false;  // Not yet supported
			}
		}
	};


	// selector extension functions
	Gimme.Selectors =
	{
		addRule: function(_symbol, _rule)
		{
			selectorRules.exRules[_symbol] = _rule;
		}

		,addPseudo: function(_name, _fn)
		{
			selectorRules.pseudo[_name] = _fn;
		}

		,addAttribute: function(_name, _fn)
		{
			selectorRules.attr[_name] = _fn;
		}
		
		,addCombinator: function(_symbol, _fn)
		{
			selectorRules.combinator[_symbol] = _fn;
		}
	};


	// initialize the parser with a set of rules
	CSSParser.setRules(selectorRules);

	// the public function for querying
	Gimme.query = Engine.query;

	// shortcut for DOM element retrieval by ID
	Gimme.id = function(_id) { return document.getElementById(_id); };


	// ------------------------------------------------
	// the gimme globals space starts here
	// ------------------------------------------------

	// for those unfortunate times when we simply /must/ write browser specific code
	var ua = navigator.userAgent.toLowerCase();
	Gimme.Browser =
	{
		isIE: typeof ActiveXObject !== 'undefined'
		,isOpera: typeof window.opera !== 'undefined'
		,isKHTML: ua.indexOf('khtml') !== -1
		,isGecko: ua.indexOf('khtml') === -1 && ua.indexOf('gecko') !== -1
		,isInIFrame: function()
		{
			try
			{
				return window.frameElement && window.frameElement.tagName === 'IFRAME';
			}
			catch (oops)
			{
				return true;		// iframes are more common than framesets (this is a tradeoff as it will yield the wrong value in cases of frameset only)
			}
		}()
		,isInFrameset: window != top		// can't use the identity operator here (IE gets it wrong)
		,isInQuirksMode: document.compatMode === 'BackCompat'
		,offsetIncludesBorders: function()
		{
		    if (typeof this.value === 'undefined')
		    {
				var a = document.createElement('div');
				a.setAttribute('style', 'position:absolute;visibility:hidden;top:0;left:0;border:1px solid #000;');
				var b = document.createElement('div');			
				a.appendChild(b);
				document.body.appendChild(a);
				this.value = offsetIncludesBorders = b.offsetTop === 1;
				document.body.removeChild(a);
				a = b = null;
		    }
		    return this.value;
		}
	};

	// a little organization for when adding expando properties to DOM elements is necessary
	var expando =
	{
		guid: '_$gimme$_guid'
		,descendant: '_$gimme$_descendant'
	};

	var g_GUIDCounter = 0;
	function getObjectGUID(_elem)
	{
		if (_elem === window)
		{
			return 'theWindow';
		}
		else if (_elem === document)
		{
			return 'theDocument';
		}
		else if (typeof _elem.uniqueID !== 'undefined')
		{
			return _elem.uniqueID;
		}

		var ex = expando.guid;
		if (typeof _elem[ex] === 'undefined')
		{
			_elem[ex] = ex + g_GUIDCounter++;
		}
		return _elem[ex];
	}

	var indexOf = function()
	{
		return typeof Array.prototype.indexOf !== 'undefined' ? native_indexOf : custom_indexOf;

		function native_indexOf(_arr, _item)
		{
			return _arr.indexOf(_item);
		}

		function custom_indexOf(_arr, _item)
		{
			var i, len = _arr.length;
			for (i = 0; i < len; i++)
			{
				if (_arr[i] === _item)
				{
					return i;
				}
			}
			return -1;
		}
	}();

	function contains(_arr, _item)
	{
		return indexOf(_arr, _item) !== -1;
	}

	function trim(_str)
	{
		return _str.replace(/^\s+|\s+$/g, '');
	}

	function normalize(_str)
	{
		return trim(_str).replace(/\s{2,}/g, ' ');
	}

	var readAttribute = function()
	{
		return Gimme.Browser.isIE ? ie_readAttribute : w3c_readAttribute;

		function w3c_readAttribute(_elem, _attr)
		{
			return _elem.getAttribute(_attr);
		}

		function ie_readAttribute(_elem, _attr)
		{
			switch (_attr.toLowerCase())
			{
				case 'class':
					return _elem.className || null;

				case 'id':
					return _elem.id || null;

				case 'href':
				case 'src':
					// IE Bug: when a '#default#userData' behavior is bound to an element, getAttribute fails to return anything useful.
					// Worse, it throws a run-time error if you try to invoke it with an iFlag parameter, as in: .getAttribute('href', 2);
					// Even more odd, typeof .getAttribute in this case returns "unknown" ( ?? what the heck! )
					// We can leverage this oddity, to avoid invoking .getAttribute(..) with an iFlag parameter.  In that case, we fall back
					// to trying to retrieve the attribute value through the element's .attributes collection.
					if (typeof _elem.getAttribute !== 'undefined')
					{
						// If typeof .getAttribute is not "unknown" we can safely use iFlag parameters when invoking .getAttribute(..).
						// It still might return null though, (in IE) so a series of logical ORs will help increase our chances of
						// retrieving the desired attribute value.
						return _elem.getAttribute(_attr, 2);
					}
					break;

				default:
					break;
			}

			return _elem.attributes && _elem.attributes[_attr] ? _elem.attributes[_attr].nodeValue : _elem.getAttribute(_attr);
		}
	}();

	var attrExists = function()
	{
		return typeof document.createElement('div').hasAttribute !== 'undefined' ? w3c_attrExists : ie_attrExists;

		function w3c_attrExists(_elem, _attr)
		{
			return _elem.hasAttribute(_attr);
		}

		function ie_attrExists(_elem, _attr)
		{
			return !!readAttribute(_elem, _attr);
		}
	}();

	function child(_direction)
	{
		var sibling = _direction + 'Sibling';

		return function(_elem)
		{
			var sib = _elem[sibling];
			while (sib && sib.nodeType !== 1)
			{
				sib = sib[sibling];
			}
			return !sib;
		}
	}
	
	function convertToPixels(_str, _context)
	{
		// if str is "false-like", treat it as 0
		if (!_str) { return 0; }

		// if the units are already pixels, no conversion is necessary, just parseInt and return
		if (/px$/.test(_str)) { return parseInt(_str, 10); }
		
		// if no _context was specified, default to the <body>
		if (!_context) { _context = document.body; }
		
		// If we get here, we've got some work to do:
		// 1. Create an invisible div that we'll use to measure the style represented by _str
		// 2. Make sure its position is absolute so it doesn't shift other elements on the page
		// 3. Make sure line-height is 0 or IE will add mysterious extra space to our measurements
		var tmp = document.createElement('div');
		tmp.style.visbility = 'hidden';
		tmp.style.position = 'absolute';				
		tmp.style.lineHeight = '0';				
		
		// Two additional checks are needed before we conver to pixels
		// 1.  If we're tyring to convert a % to pixels, we have to do it in the context of a parent element
		// 2.  Or if the _context element is an <img>, we also need to use the parent element, b/c we're going to append the
		// invisible div (tmp) to the _context, but it's not valid to append an element to an <img> tag
		if (/%$/.test(_str) || _context.tagName === 'IMG')
		{
			_context = _context.parentNode || _context;
			tmp.style.height = _str;
		}
		else
		{
			tmp.style.borderStyle = 'solid';
			tmp.style.borderBottomWidth = '0';					
			tmp.style.borderTopWidth = _str;
		}
		
		_context.appendChild(tmp);
		var px = tmp.offsetHeight;
		_context.removeChild(tmp);
		
		return px || 0;	
	}

	// the functions defined above have usefulness outside of the this closure, so we expose them here under the "Helper" moniker.
	Gimme.Helper =
	{
		getObjectGUID: getObjectGUID
		,indexOf: indexOf
		,contains: contains
		,trim: trim
		,normalize: normalize
		,hasClass: selectorRules.inRules['.'].processFn
		,readAttribute: readAttribute
		,attrExists: attrExists
		,convertToPixels: convertToPixels
	};

})();

/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Object Module:	Wrapper around Gimme Query
*
*	Requires:
*	Gimme.query.js
*
*/

// Wrap a new Gimme.object(..) invocation in the function g(..)
function g(selector)
{
	return new Gimme.object(Gimme.query(selector));
}

Gimme.object = function(elems)
{
	this.entities = elems;
	this.length = this.entities.length;
};
Gimme.ext = Gimme.object.prototype;

(function()
{
	var h = Gimme.Helper
	,contains = h.contains
	,indexOf = h.indexOf
	,trim = h.trim
	,normalize = h.normalize
	,hasClass = h.hasClass
	,attrExists = h.attrExists
	,readAttr = h.readAttribute
	,getObjectGUID = h.getObjectGUID
	,convertToPixels = h.convertToPixels;

	Gimme.ext.element = function(n)
	{
		return this.entities[n || 0];
	};

	Gimme.ext.parent = function(n)
	{
		return this.entities[n || 0].parentNode;
	};

	Gimme.ext.addClass = function(_class)
	{
		var classes = _class.split(/\s+/);
		
		this.forEach(function(_elem)
		{
			g(classes).forEach(function(_className)
			{
				if (!hasClass(_elem, [ _className ]))
				{
					if (_elem.className === '')
					{
						_elem.className = _className;
					}
					else
					{
						_elem.className += ' ' + _className;
					}
				}
			});
		});
		
		return this;
	};

	Gimme.ext.removeClass = function(_class)
	{
		return this.swapClass(_class, '$1');
	};

	Gimme.ext.swapClass = function(_oldClass, _newClass)
	{
		// if _newClass is '$1' it means we're actually just removing _oldClass (and not replacing it with anything)
		// however, if _newClass is anything other than $1, we need to pad it with spaces 
		if (_newClass !== '$1')	{ _newClass = ' ' + _newClass + ' '; }
		
		var classes = _oldClass.split(/\s+/);
		
		this.forEach(function(_elem)
		{
			var tmpClass = _elem.className;
			
			g(classes).forEach(function(_className)
			{
				var pattern = new RegExp('(^| )' + _className + '( |$)');
				tmpClass = tmpClass.replace(pattern, _newClass);
			});
			
			_elem.className = normalize(tmpClass);
		});
		
		return this;
	};

	Gimme.ext.hasClass = function(_class, n)
	{
		return hasClass(this.entities[n || 0], [ _class ]);
	};

	Gimme.ext.getAncestor = function(_distance, n)
	{
		var elem = this.entities[n || 0];
		var i = _distance;
		while (i-- > 0)
		{
			if (elem)
			{
				elem = elem.parentNode;
			}
			else
			{
				break;
			}			
		}
		
		return elem;
	};

	Gimme.ext.getSibling = function(_distance, n)
	{
		var elem = this.entities[n || 0];
		
		if (_distance === 0)
		{
			return elem;
		}
		
		var sibling = _distance > 0 ? 'nextSibling' : 'previousSibling';

		var sib = elem;
		var count = Math.abs(_distance);

		var i = 0;
		while (i < count)
		{
			sib = sib[sibling];
			if (!sib)
			{
				break;
			}
			if (sib.nodeType === 1)
			{
				i++;
			}
		}

		return sib;
	};

	Gimme.ext.select = function(_selector)
	{
		return new Gimme.object(Gimme.query(_selector, this.entities[0]));
	};

	Gimme.ext.setHTML = function(_html)
	{
		this.forEach(function(_elem)
		{
			_elem.innerHTML = _html;
		});

		return this;
	};

	Gimme.ext.getHTML = function(n)
	{
		return this.entities[n || 0].innerHTML;
	};

	Gimme.ext.setValue = function(_val)
	{
		this.forEach(function(_elem)
		{
			if (typeof _elem.value !== 'undefined')
			{
				_elem.value = _val;
			}
		});
			
		return this;
	};

	Gimme.ext.getValue = function(n)
	{
		return this.entities[n || 0].value || '';
	};

	Gimme.ext.readAttribute = function(_attr, n)
	{
		return readAttr(this.entities[n || 0], _attr);
	};

	Gimme.ext.writeAttribute = function(_attr, _val)
	{
		this.forEach(function(_elem)
		{
			_elem.setAttribute(_attr, _val);
		});
		
		return this;
	};

	Gimme.ext.filter = function(_filterFn)
	{
		var entities = this.entities;
		var i = 0, len = entities.length;
		while (i < len)
		{
			if (!_filterFn(entities[i]))
			{
				entities.splice(i, 1);
				len--;
			}
			else
			{
				i++;
			}
		}
		
		return this;
	};

	Gimme.ext.iterate = function(_callback)
	{
		var entities = this.entities;
		var i, len = entities.length;
		for (i = 0; i < len; i++)
		{
			var gElem = g(entities[i]);
			_callback.call(gElem, i);
		}
		
		_callback = null;
		
		return this;
	};

	Gimme.ext.getStyle = function(_style, n, _smart)
	{
		var elem = this.entities[n || 0];
		
		// TODO:  Document this rather confusing piece of code and why it's necessary
		if (_style === 'opacity')
		{
			var opacity = getOpacity(elem);
			if (isNaN(opacity))
			{
				_style = opacity;
			}
			else
			{
				return opacity;
			}
		}
		
		// _smart, which default to true, means that the function will attempt to calculate
		// the 'bottom' or 'right' styles based on the 'top' or 'left' styles (if it needs to)
		if (_smart !== false) { _smart = true; }

		var computedStyle = '';						// will be set to a computedStyle object in W3C browsers, and a currentStyle object in IE
		
		// if the browser supports .defaultView.getComputedStyle, life is easy -- just use that
		if (typeof document.defaultView !== 'undefined' && typeof document.defaultView.getComputedStyle !== 'undefined')
		{
			computedStyle = document.defaultView.getComputedStyle(elem, null);
		}
		// otherwise, crap, we've got work to do
		else if (typeof elem.currentStyle !== 'undefined')
		{	
			// start by retrieving the element's .currentStyle property (this won't be normalized to pixels)
			computedStyle = elem.currentStyle;
			
			// If the style being requested is either of the positions: top or left, we'll use .offsetTop and .offsetLeft
			// * WARNING:
			// * Browsers have sharp disagreements about what .offsetTop and .offsetLeft mean, but it just so "happens"
			// * that in IE .offsetTop and .offsetLeft are equal to the computed top and the computed left. Firefox usually
			// * agrees with IE on this, but Konqueror, Opera, and Safari all disagree (with IE and FF).
			// * The major flaw in this function's logic is that it assumes .offsetTop and .offsetLeft are going to be the same
			// * as the computed top and computed left if the browser supports .currentStyle (and not .defaultView.getComputedStyle).
			// * At present, this appears to be true, and I suspect that it will always be true, but there is no guarantee.				
			
			// attempt to get the requested style
			var val = computedStyle[_style];
			
			// if the value is 'auto', it most likely means that it was never specified in the stylesheet
			if (val === 'auto')
			{
				// if height was requested, try .clientHeight; if that fails, try .offsetHeight
				if (_style === 'height')
				{
					var padding = parseInt(g(elem).getStyle('paddingTop')) + parseInt(g(elem).getStyle('paddingBottom'));
					if (elem.clientHeight)
					{
						return (elem.clientHeight - padding) + 'px';
					}					
					else
					{
						return (elem.offsetHeight - padding - parseInt(g(elem).getStyle('borderTopWidth')) - parseInt(g(elem).getStyle('borderBottomWidth'))) + 'px';
					}					
				}
				
				// if width was requested, try .clientWidth; if that fails, try .offsetWidth
				if (_style === 'width')
				{
					var padding = parseInt(g(elem).getStyle('paddingLeft')) + parseInt(g(elem).getStyle('paddingRight'));
					if (elem.clientWidth)
					{
						return (elem.clientWidth - padding) + 'px';
					}
					else
					{
						return (elem.offsetWidth - padding - parseInt(g(elem).getStyle('borderLeftWIdth')) - parseInt(g(elem).getStyle('borderRightWidth'))) + 'px';
					}
				}
				
				// if top was requested, use .offsetTop
				if (_style === 'top')
				{
					return elem.offsetTop + 'px';
				}
				// if left was requested, use .offsetLeft
				else if (_style === 'left')
				{
					return elem.offsetLeft + 'px';
				}
				// If bottom or right were requested, AND if _smart (true by default) was specified, then
				// try to calculate right based on left or bottom based on top
				else if (_smart && (_style === 'right' || _style === 'bottom'))
				{
					// flip serves as an easy was to request the compliment property
					var flip = { bottom: [ 'top', 'Height' ], right: [ 'left', 'Width' ] };
					
					// get the computedStyle of the compliment property (make sure _smart is false so we don't recurse infinitely)
					var compliment = parseInt(this.getComputedStyle(elem, flip[_style][0], false), 10);
					
					// return the calculated bottom or right based on the known top or left
					return (elem.parentNode['client' + flip[_style][1]] - elem['offset' + flip[_style][1]] - compliment) + 'px';
				}
				// if val is 'auto', but none of the above apply, assume 0px (this might not be safe -- need to look into this more)					
				else
				{
					return '0px';
				}
			}
			
			// Some values should not be converted to pixels (like hex color values for instance), so this regex aids in
			// determining when to try converting to pixels (specifically, only for valid units that are not pixels)
			var nonPixels = /(em|ex|%|in|cm|mm|pt|pc|small|medium|large|thin|thick)$/;
			
			// Based on the 
			var borderWidth = /border(.*)Width/i;
			var borderStyle = borderWidth.test(_style) ? _style.replace(borderWidth, 'border$1Style') : null;				
			
			// only bother attempting to convert valid units that are not already pixels
			if (nonPixels.test(val))
			{					
				// IE does something interesting.  If you ask for border[Top|Right|Bottom|Left]Width, IE will give it to you... NO MATTER WHAT!
				// Depending on who you ask, this may or may not make sense.  But it doesn't matter, it won't work for this function.
				// If, for example, the borderTopWidth is 5px, but the borderTopStyle is 'none', then we need to report borderTopWidth as 0!
				if (borderStyle !== null && computedStyle[borderStyle] === 'none')
					{ return '0px'; }
				else
					{ return h.convertToPixels(val, elem); }
			}
		}
		return computedStyle && computedStyle[_style];
	};

	Gimme.ext.setStyle = function(_style, _val)
	{
		return g(this.entities).setStyles(_style, _val);		
	};
	
	Gimme.ext.setStyles = function(/* variables number of arguments accepts*/)
	{
		var styleName
		,styleVal
		,args = arguments
		,len = args.length
		,i;
		
		if (len % 2 !== 0)
		{
			return;
		}
		
		this.forEach(function(_elem)
		{
			for (i = 0; i < len; i += 2)
			{
				styleName = args[i];
				styleVal = args[i + 1];
				if (styleName === 'opacity')
				{
					setOpacity(_elem, styleVal);
				}
				else
				{
					_elem.style[styleName] = styleVal;
				}
			}
		});
		
		return this;
	};
	
	Gimme.ext.addEvent = function()
	{
		if (typeof document.addEventListener !== 'undefined')
		{
			return w3c_addEvent;
		}
		else if (typeof document.attachEvent !== 'undefined')
		{
			return ie_addEvent;
		}
		else
		{
			// no modern event support I guess :-(
			return function() { };
		}
		
		function w3c_addEvent(_evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = this['on' + _evtName];
						
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(this, _fn, _useCapture, true);
			}
			else
			{
				this.forEach(function(_elem)			
				{
					_elem.addEventListener(_evtName, _fn, _useCapture);
				});
			}
			
			return this;
		}
		
		function ie_addEvent(_evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = this['on' + _evtName];
						
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(this, _fn, _useCapture, true);
			}
			else
			{
				this.forEach(function(_elem)
				{
					// create a key to identify this element/event/function combination
					var key = '{' + getObjectGUID(_elem) + '/' + _evtName + '/' + getObjectGUID(_fn) + '}';
					
					// if this element/event/combo has already been wired up, just return
					var f = evtHash[key];
					if (typeof f !== 'undefined')
						{ return; }
					
					// create a helper function to fix IE's lack of standards support
					f = function(e)
					{
						// map .target to .srcElement
						e.target = e.srcElement;
						
						// map .relatedTarget to either .toElement or .fromElement
						if (_evtName == 'mouseover') { e.relatedTarget = e.fromElement; }
						else if (_evtName == 'mouseout') { e.relatedTarget = e.toElement; }
							
						e.preventDefault = function() { e.returnValue = false; };
						e.stopPropagation = function() { e.cancelBubble = true; };
		
						// call the actual function, using entity (the element) as the 'this' object
						_fn.call(_elem, e);
						
						// null out these properties to prevent memory leaks
						e.target = null;
						e.relatedTarget = null;
						e.preventDefault = null;
						e.stopPropagation = null;
						e = null;
					};
					
					// add the helper function to the event hash
					evtHash[key] = f;

					// hook up the event (IE style)
					_elem.attachEvent('on' + _evtName, f);
					
					key = null;
					f = null;
				});
			}
			
			return this;
		}
	}();
	
	Gimme.ext.removeEvent = function()
	{
		if (typeof document.removeEventListener !== 'undefined')
		{
			return w3c_removeEvent;
		}
		else if (typeof document.detachEvent !== 'undefined')
		{
			return ie_removeEvent;
		}
		else
		{
			// no modern event support I guess :-(
			return function() { };
		}
		
		function w3c_removeEvent(_evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = this['on' + _evtName];
						
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(this, _fn, _useCapture, false);
			}
			else
			{
				this.forEach(function(_elem)
				{
					_elem.removeEventListener(_evtName, _fn, _useCapture);
				});
			}
			
			return this;
		}
		
		function ie_removeEvent(_evtName, _fn, _useCapture, _directCall)
		{
			var eventFn = this['on' + _evtName];
						
			if (typeof eventFn === 'function' && _directCall !== false)
			{
				eventFn.call(this, _fn, _useCapture, false);
			}
			else
			{
				this.forEach(function(_elem)
				{
					// grab the event handler function from the hash based on its key
					var key = '{' + getObjectGUID(_elem) + '/' + _evtName + '/' + getObjectGUID(_fn) + '}';
					var f = evtHash[key];
					if (typeof f !== 'undefined')
					{
						// unhook the event and delete the element from the event hash
						_elem.detachEvent('on' + _evtName, f);
						delete evtHash[key];
					}

					// prevent IE memory leaks
					key = null;
					f = null;
				});
			}
			
			return this;
		}
	}();
	
	Gimme.ext.forEach = function()
	{
		return typeof Array.prototype.forEach !== 'undefined' ? native_forEach : custom_forEach;
		
		function native_forEach(_fn, _thisObject)
		{
			this.entities.forEach(_fn, _thisObject);
			return this;
		}
		
		function custom_forEach(_fn, _thisObject)
		{
			var things = this.entities;
			var i, elem, len = things.length;
			for (i = 0; i < len; i++)
			{
				elem = things[i];
				_fn.call(_thisObject, elem, i, things);
			}
			
			return this;
		}
	}();

	Gimme.ext.map = function()
	{
		return typeof Array.prototype.map !== 'undefined' ? native_map : custom_map;
		
		function native_map(_fn, _thisObject)
		{
			return this.entities.map(_fn, _thisObject);
		}
		
		function custom_map(_fn, _thisObject)
		{
			var result = [];
			this.forEach(function(_el, _i)
			{
				result.push(_fn.call(_thisObject, _el));
			});
			
			return result;
		}
	}();

	Gimme.ext.contains = function(_item)
	{
		return contains(this.entities, _item);
	};

	Gimme.ext.indexOf = function(_item)
	{
		return indexOf(this.entities, _item);
	};	

	
	// Helper Area: private members and functions
	//================================
	
	// private members
	var evtHash = {};
	
	// private functions
	var setOpacity = function()
	{
		function w3c_opacity(_elem, _amt) { _elem.style.opacity = _amt; }		
		function ie_opacity(_elem, _amt) { _elem.style.filter = 'alpha(opacity=' + (_amt * 100) + ')'; }
	
		var fn, d = document.createElement('div');
		if (typeof d.style.opacity !== 'undefined')
		{
			fn = w3c_opacity;
		}
		else if (typeof d.style.filter !== 'undefined')
		{
			fn = ie_opacity;
		}
		else
		{
			fn = function() {};			// No opacity support I guess :-(
		}
		
		d = null;
		return fn;	
	}();

	var getOpacity = function()
	{
		function w3c_opacity(_elem)	{ return parseFloat(_elem.style.opacity) || 'opacity';	}
		function ie_opacity(_elem)
		{
			var filter = _elem.currentStyle.filter;
			var match = filter.match(/pacity\s*=\s*(\d{1,3}.?\d*)\)/);
			if (!match)
			{
				return 1;
			}
			else
			{
				return parseFloat(match[1]) / 100;
			}
		}
		
		var fn, d = document.createElement('div');
		if (typeof d.style.opacity !== 'undefined')
		{
			fn = w3c_opacity;
		}
		else if (typeof d.style.filter !== 'undefined')
		{
			fn = ie_opacity;
		}
		else
		{
			fn = function() {};			// No opacity support I guess :-(
		}
		
		d = null;
		return fn;	
	}();
	
})();

/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Events Module / Extensions:	Provides a "home" for event-related functionality and defines some event extensions which qualize browser differences among several proprietary events
*
*	Requires:
*	Gimme.query.js
*	Gimme.object.js
*
*/

Gimme.Events = new function()
{
	var evtHash = {};
	var capturingElem = null;
	var rerouteFn = null;
	
	this.captureMouse = function(_elem)
	{
		Gimme.Events.releaseMouse();
		capturingElem = _elem;
		
		if (typeof _elem.setCapture !== 'undefined')
		{
			_elem.setCapture();
		}
		else
		{
			rerouteFn = function(e)
			{
				e.stopPropagation();
				
				var synthEvent, scrollPos;
				if (Gimme.Browser.isGecko)
				{
					synthEvent = document.createEvent('MouseEvents');
					synthEvent.initMouseEvent(
						e.type							// type
						,e.bubbles						// can bubble?
						,e.cancelable					// cancelable?
						,window							// always window here
						,e.detail						// mouse click count
						,e.screenX						// event's screen x coordinate (copied from the passed in event)
						,e.screenY						// event's screen y coordinate (copied from the passed in event)
						,e.clientX						// event's client x coordinate (copied from the passed in event)
						,e.clientY						// event's client y coordinate (copied from the passed in event)
						,e.ctrlKey						// whether or not the ctrl key was pressed during the event (copied from the passed in event)
						,e.altKey						// whether or not the alt key was pressed during the event (copied from the passed in event)
						,e.shiftKey						// whether or not the shift key was pressed during the event (copied from the passed in event)
						,e.metaKey						// whether or not the meta key was pressed during the event (copied from the passed in event)
						,e.button						// indicates which button (if any) caused the mouse event (copied from the passed in event)
						,e.relatedTarget				// relatedTarget (only applicable for 'mouseover' and 'mouseout' events)
					);
					
					scrollPos = Gimme.Screen.getScrollPosition();
					synthEvent.__defineGetter__('pageX', function() { return this.clientX + scrollPos.x; });
					synthEvent.__defineGetter__('pageY', function() { return this.clientY + scrollPos.y; });
				}
				else
				{
					synthEvent = e;
				}
				
				document.removeEventListener(e.type, rerouteFn, true);
				synthEvent.captureTarget = e.target;
				_elem.dispatchEvent(synthEvent);
				if (rerouteFn !== null)
				{
					document.addEventListener(e.type, rerouteFn, true);
				}
				delete synthEvent.captureTarget;				
			}
			
			document.addEventListener('mouseover', rerouteFn, true);
			document.addEventListener('mouseout', rerouteFn, true);
			document.addEventListener('mousemove', rerouteFn, true);
			document.addEventListener('mouseup', rerouteFn, true);
			document.addEventListener('mousedown', rerouteFn, true);
			document.addEventListener('click', rerouteFn, true);
			document.addEventListener('dblclick', rerouteFn, true);
		}
		
		return this;
	};
	
	this.releaseMouse = function()
	{
		if (capturingElem !== null)
		{
			if (typeof capturingElem.releaseCapture !== 'undefined')
			{
				capturingElem.releaseCapture();
			}
			else
			{
				document.removeEventListener('mouseover', rerouteFn, true);
				document.removeEventListener('mouseout', rerouteFn, true);
				document.removeEventListener('mousemove', rerouteFn, true);
				document.removeEventListener('mouseup', rerouteFn, true);
				document.removeEventListener('mousedown', rerouteFn, true);
				document.removeEventListener('click', rerouteFn, true);
				document.removeEventListener('dblclick', rerouteFn, true);
			}
			
			capturingElem = rerouteFn = null;
		}
		
		return this;
	};

	this.getCaptureTarget = function(_evt)
	{
		return _evt.captureTarget || _evt.srcElement || _evt.target;
	};
	
	Gimme.ext.onmouseenter = function(_fn, _useCapture, _listening)
	{
		var f = mouseEnter(_fn);
		_listening ? this.addEvent('mouseover', f, _useCapture, false) : this.removeEvent('mouseover', f, _useCapture, false);
		f = null;
	};

	Gimme.ext.onmouseleave = function(_fn, _useCapture, _listening)
	{
		var f = mouseEnter(_fn);
		_listening ? this.addEvent('mouseout', f, _useCapture, false) : this.removeEvent('mouseout', f, _useCapture, false);
		f = null;
	}

	Gimme.ext.onmousewheel = function(_fn, _useCapture, _listening)
	{
		var evtName = 'mousewheel'
		,f = _fn;
		
		if (Gimme.Browser.isGecko)
		{
			evtName = 'DOMMouseScroll';
			f = mouseWheel(_fn);
		}
		
		_listening ? this.addEvent(evtName, f, _useCapture, false) : this.removeEvent(evtName, f, _useCapture, false);
	};
	
	
	// Private helper functions

	function isAnAncestorOf(_ancestor, _descendant, _generation)
	{
		if (_ancestor === _descendant) { return false; }
		
		var gen = 0;
		while (_descendant && _descendant != _ancestor)
		{
			gen++;
			_descendant = _descendant.parentNode;
		}
		
		_generation = _generation || gen;
		return _descendant === _ancestor && _generation === gen;		
	}
	
	function mouseEnter(_fn)
	{	
		var key = Gimme.Helper.getObjectGUID(_fn);
		var f = evtHash[key];
		if (typeof f === 'undefined')
		{
			f = evtHash[key] = function(_evt)
			{
				var relTarget = _evt.relatedTarget;
				if (this === relTarget || isAnAncestorOf(this, relTarget)) { return; }
		
				_fn.call(this, _evt);
			};
		}
		return f;	
	}
	
	function mouseWheel(_fn)
	{
		var key = Gimme.Helper.getObjectGUID(_fn);
		var f = evtHash[key];
		if (typeof f === 'undefined')
		{
			f = evtHash[key] = function(_evt)
			{
				_evt.wheelDelta = -(_evt.detail);
				_fn.call(this, _evt);
				_evt.wheelDelta = null;
			}
		}
		return f;
	}
};

/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme AJAX Module:	Helper methods designed to ease management of asynchronous requests in Javascript
*
*	Requires:
*	gimme.query.js
*
*/

Gimme.AJAX = new function()
{
	this.requestJSON = function(_uri, _callback)
	{
		doAsyncRequest(_uri, _callback, true);
	};

	this.requestAHAH = function(_uri, _callback)
	{
		doAsyncRequest(_uri, _callback, false);
	};

	function createXHR()
	{
		if (typeof XMLHttpRequest !== 'undefined')
		{
			return new XMLHttpRequest();
		}
		else if (typeof ActiveXObject !== 'undefined')
		{
			try { return new ActiveXObject('Microsoft.XMLHTTP'); }
			catch(everything) { throw new Error('Error invoking XMLHTTP'); }
		}
		else
		{
			throw new Error('XMLHttp is not supported');
		}
	}

	// XHR Stuff
	function doAsyncRequest(_uri, _callback, _isJSON)
	{
		var xhr = createXHR();
		xhr.onreadystatechange = function()
		{
			if (xhr.readyState === 4)
			{
				if (xhr.status === 200)
				{
					var data = xhr.responseText;
					if (_isJSON) { data = eval('(' + xhr.responseText + ')'); }
					_callback.call(xhr, data);
				}

				xhr = null;
				callback = null;
			}
		};
		xhr.open('GET', _uri, true);
		xhr.send('');
	}
};

/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Utilities Module:	Miscellaneous helper methods that just don't fit anywhere else.
*
*	Requires:
*	gimme.query.js
*
*/

Gimme.Util = new function()
{
	this.setTimeout = function(_fn, _milliseconds)
	{
		return delayInvoke(arguments, false);
	};
	
	this.setInterval = function(_fn, _milliseconds)
	{
		return delayInvoke(arguments, true);
	};
	
	function delayInvoke(_args, _repeat)
	{
		var fn = _args[0];
		var milliseconds = _args[1];
		function proxy()
		{
			fn.apply(this, Array.prototype.slice.call(_args, 2));
			if (!_repeat) { fn = null; }
		}
		
		if (_repeat === true)
		{
			// Warning:  this will most likely cause memory leak issues in IE
			// TODO:  create a custom clearInterval function that nulls out the function when its called?
			return window.setInterval(proxy, milliseconds);
		}
		else
		{
			return window.setTimeout(proxy, milliseconds);
		}		
	}
};

/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Screen Module:		Helper methods dealing with screen geometry (mouse position, scroll position, viewport size, etc...)
*	Gimme Screen Extensions:	g(..) extensions for retrieving DOM element position information
*
*	Requires:
*	gimme.query.js
*	gimme.object.js
*
*/

Gimme.Screen = new function()
{
	this.getViewportSize = function()
	{
		var size = { width: 0, height: 0 };
		if (typeof window.innerWidth !== 'undefined')
		{
			size = { width: window.innerWidth, height: window.innerHeight };
		}
		else if (typeof document.documentElement !== 'undefined' && typeof document.documentElement.clientWidth !== 'undefined' && document.documentElement.clientWidth !== 0)
		{
			size = { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
		}
		else
		{
			size = { width: document.getElementsByTagName('body')[0].clientWidth, height: document.getElementsByTagName('body')[0].clientHeight };
		}

		return size;
	};

	this.getMousePosition = function(_evt)
	{
		if (!_evt)
		{
			_evt = window.event;
		}

		var cursorPosition = { x: 0, y: 0 };

		// Standard way of obtaining cursor position relative to the entire page is via .pageX and .pageY.
		// IE-Mac has .pageX/Y but its value is wrong, so we check for a non-standard .x property (which IE-Mac doesn't have)
		// to ensure that that browser won't fall into this if block.
		if (typeof _evt.pageX !== 'undefined' && typeof _evt.x !== 'undefined')
		{
			cursorPosition.x = _evt.pageX;
			cursorPosition.y = _evt.pageY;
		}
		else
		{
			// The .clientX/Y properties measure the distance from the mouse cursor to the edges of the browser window.  In order to find the
			// mouse position relative to the entire page, we need to add scroll position to their values.
			// With the exception of Safari, the following is a cross-browser way of obtaining mouse position.  Safari incorrectly give .clientX/Y
			// the same value as .pageX/Y, which is why we need to use .pageX/Y above if it's available
			var scrollPosition = this.getScrollPosition();
			cursorPosition.x = _evt.clientX + scrollPosition.x;
			cursorPosition.y = _evt.clientY + scrollPosition.y;
		}

		return cursorPosition;
	};

	this.getScrollPosition = function()
	{
		var position = { x: 0, y: 0 };

		// window.pageYOffset is used by Firefox and Mozilla browsers, Safari, Opera, and Konqueror
		if (typeof window.pageYOffset !== 'undefined')
		{
			position.x = window.pageXOffset;	
			position.y = window.pageYOffset;
		}
		// document.documentElement.scrollTop is used by IE6 in standards-compliant mode
		else if (!Gimme.Browser.isInQuirksMode && typeof document.documentElement.scrollTop !== 'undefined')
		{
			position.x = document.documentElement.scrollLeft;
			position.y = document.documentElement.scrollTop;
		}
		// document.body.scrollTop is used by IE6 in "Quirks" mode
		else if (typeof document.body.scrollTop !== 'undefined')
		{
			position.x = document.body.scrollLeft;
			position.y = document.body.scrollTop;
		}

		return position;
	};
};

// Gimme Screen Extensions

// retrieves the absolute screen position of the nth element in the entities array (n defaults to 0)
Gimme.ext.getPosition = function(_accountForPageScroll, n)
{
	var elem = this.entities[n || 0]
	,useScroll
	,scrollPos
	,box
	,offset
	,xPos = 0
	,yPos = 0;

	if (!Gimme.Browser.isOpera && typeof elem.getBoundingClientRect !== 'undefined')
	{
		/*
		.getBoundingClientRect() returns a TextRectangle object whose coordinate values are relative to the client's
		upper-left corner.  In IE/Win5+, the window's upper-left is at 2,2 (pixels) with respect to the true client.
		See: http://msdn2.microsoft.com/en-us/library/ms536433.aspx
		
		Note however, that there are exceptions to the (2,2).  It seems that if the page is running within a frameset,
		the (2, 2) does not apply.  If it's running in a IFrame though, it still does.  What if it's running in a IFrame
		that's running in a Frameset?  No idea -- that kind of HTML should be illegal anyway.
		*/
		offset = !Gimme.Browser.isInIFrame && Gimme.Browser.isInFrameset ? 0 : 2;
		box = elem.getBoundingClientRect();
		xPos = box.left - offset;
		yPos = box.top - offset;
		
		/*
		In the case of .getBoundingClientRect(), the _accountForPageScroll bit needs to be interpreted backwards,
		because .getBoundingClientRect() already factors out page scroll and returns the screen position.  This is
		the exact opposite of what we do when manually calculating potision via the while loop below (that returns
		the page position by default, and so scroll position would have to be /subtracted/ out if screen position
		is desired.		
		*/
		if (!_accountForPageScroll)
		{
			scrollPos = Gimme.Screen.getScrollPosition();
			xPos += scrollPos.x;
			yPos += scrollPos.y;			
		}
		else
		{
			_accountForPageScroll = false;
		}
	}
	else
	{
		while (elem !== null)
		{
			useScroll = typeof elem.scrollTop !== 'undefined'
						&& elem !== document.body
						&& elem !== document.documentElement
						&& elem.tagName !== 'TEXTAREA'
						&& elem.tagName !== 'INPUT'
						? 1 : 0;
						
			xPos += elem.offsetLeft - (useScroll * elem.scrollLeft);
			yPos += elem.offsetTop - (useScroll * elem.scrollTop);
			elem = elem.offsetParent;
			
			if (elem && !Gimme.Browser.offsetIncludesBorders())
			{
				xPos += parseInt(g(elem).getStyle('borderLeftWidth')) || 0;
				yPos += parseInt(g(elem).getStyle('borderTopWidth')) || 0;
			}
		}
	}
	
	/*
	If screen position is desired, subtract out the window's horizontal and vertical scroll values
	from the current xPos and yPos values.
	*/
	if (_accountForPageScroll)
	{
		scrollPos = Gimme.Screen.getScrollPosition();
		xPos -= scrollPos.x;
		yPos -= scrollPos.y;
	}	
	
	return { x: xPos, y: yPos };
};

Gimme.ext.getScreenPosition = function(n)
{
	return this.getPosition(true, n);
};

Gimme.ext.getPagePosition = function(n)
{
	return this.getPosition(false, n);
};

// retrieves the relative position of the nth element in the entities array (n defaults to 0)
Gimme.ext.getComputedPosition = function(n)
{
	var elem = this.entities[n || 0];
	return { x: parseInt(g(elem).getStyle('left'), 10), y: parseInt(g(elem).getStyle('top'), 10) };
};

/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Animation Module:	Bezier Curve Interpolation, plus a number of default curves to aid in creating animations in Javascript.
*						This module is designed to work with the Gimme Effects extension.
*
*	Requires:
*	gimme.query.js
*
*/


Gimme.Animation = new function()
{
    var m_animationHash = {};
	var m_grouping = false;
	
	this.Speeds =
	{
		snail: 2000,
		turtle: 1250,
		slowly: 1250,
		rabbit: 1000,
		greyhound: 750,
		quickly: 750,
		cheetah: 500,
		lightning: 250
	};
	
	this.Directions = 
	{
		vertically: 1,
		horizontally: 2,
		both: 3
	};
    
	this.start = function(_guid, _fn, _milliseconds)
	{
		var iid = setInterval(_fn, _milliseconds);
		
		var obj = m_animationHash[_guid];
		if (typeof obj === 'undefined')
		{
			m_animationHash[_guid] = { iids: [ iid ], callback: null };
		}
		else
		{
			obj.iids.push(iid);
		}
	};
    
    this.end = function(/*_guid*/)
    {
		var i, len = arguments.length;
		for (i = 0; i < len; i++)
		{
			var guid = arguments[i];
	        var obj = m_animationHash[guid];
	        if (typeof obj !== 'undefined')
	        {
				g(obj.iids).forEach(function(_iid, _index)
				{
					clearTimeout(_iid);
				});

				if (typeof obj.callback === 'function')
				{
					obj.callback.call();
					obj.callback = null;
				}
				delete m_animationHash[guid];			
	        }
		}
    };
	
	this.isRunning = function(_guid)
	{
		return typeof m_animationHash[_guid] !== 'undefined';
	};
	
	this.whenDone = function(_guid, _callback)
	{
		var obj = m_animationHash[_guid];
		if (typeof obj === 'undefined')
		{
			m_animationHash[_guid] = { iids: [], callback: _callback };
		}
		else
		{
			obj.callback = _callback;
		}
	};
	
	this.startGroup = function()
	{
		m_grouping = true;
	};
	
	this.endGroup = function()
	{
		m_grouping = false;
	};
	
	this.isGrouping = function()
	{
		return m_grouping === true;
	};
};


// BEZIER CURVE CLASS
Gimme.Animation.BezierCurve = function(/* controlPoints, pointsPerCurve (both pulled from arguments) */)
{
	this.points = [];
	this.args = arguments;
};
Gimme.Animation.BezierCurve.prototype.initialize = function()
{
	var args = this.args;
	var argsLen = args.length;
	var pointsPerCurve = args[argsLen - 1] || 100;
	var numCurves = argsLen - 1;

	var i, j, n, factorials, controlPoints;
	for (i = 0; i < numCurves; i++)
	{
		controlPoints = args[i];
		factorials = getFactorials(controlPoints.length);
		n = controlPoints.length - 1;
		for (j = 0; j <= pointsPerCurve; j++)
		{
			this.points.push(computePoint(j / pointsPerCurve));
		}
	}
	
	this.args = args = null;
	return pointsPerCurve;
	
	
	// Private helper functions
	function computePoint(t)
	{
		var i, sumX = 0, sumY = 0;
		for (i = 0; i <= n; i++)
		{
			var n_choose_i = factorials[n] / (factorials[i] * factorials[n - i]);
			var one_minus_t_pow_n_minus_i = Math.pow(1 - t, n - i);
			var t_pow_i = Math.pow(t, i);
		
			sumX += n_choose_i * controlPoints[i].x * one_minus_t_pow_n_minus_i * t_pow_i;
			sumY += n_choose_i * controlPoints[i].y * one_minus_t_pow_n_minus_i * t_pow_i;
		}
		
		return { x: sumX, y: sumY };
	}
	
	function getFactorials(n)
	{
	    var i, curr = 1, factorials = [1];
	    for (i = 1; i <= n; i++)
	    {
			curr *= i;
			factorials.push(curr);
	    }
		
	    return factorials;
	}
};
Gimme.Animation.BezierCurve.prototype.getPoint = function(_t)
{
	var pointsLen = this.points.length;
	if (pointsLen === 0)
	{
		pointsLen = this.initialize();
	}
	
	var index = Math.floor(_t * pointsLen);
	if (index > pointsLen - 1)
	{
		index = pointsLen - 1;
	}
	
	return this.points[index];
};
// END BEZIER CURVE CLASS

// ACCELERATION LINE CLASS
Gimme.Animation.AccelerationLine = function(_pointCluster, _numPoints)
{
	var interval = _pointCluster[_pointCluster.length - 1];
	var pointCluster = g(_pointCluster).map(function(_val)
	{
		return { x: _val / interval, y: 0 };
	});
	
	this.bezier = new Gimme.Animation.BezierCurve(pointCluster, _numPoints);
	this.points = this.bezier.points;
};
Gimme.Animation.AccelerationLine.prototype.getValue = function(_t)
{
	return this.bezier.getPoint(_t).x;
};
// END ACCELERATION LINE CLASS

// STOCK ACCELERATIONLINES
Gimme.Animation.AccelerationLines =
{
	zoom: new Gimme.Animation.AccelerationLine(
		[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
		500,
		501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520], 75),
		
	slowStartAccelerate: new Gimme.Animation.AccelerationLine(
		[0, 1, 2, 3, 8, 50], 100),
		
	quickStartDecelerate: new Gimme.Animation.AccelerationLine(
		[0, 50, 55, 56, 57, 58], 100),
	
	linear: new Gimme.Animation.AccelerationLine(
		[0,10], 100)
};
Gimme.Animation.AccelerationLines.defaultLine = Gimme.Animation.AccelerationLines.zoom;

/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Effects Extension:	Various "glitzy" effect (powered mostly by the Gimme Animation Module) that can be performed on Gimme objects.
*
*	Requires:
*	gimme.query.js
*	gimme.object.js
*
*/

Gimme.ext.fadeIn = function(_duration, _guid, _callback)
{
	g(this.entities).fadeTo(null, 0.99999, _duration, _guid, _callback);
	_callback = null;
	return this;
};

Gimme.ext.fadeOut = function(_duration, _guid, _callback)
{
	g(this.entities).fadeTo(null, 0, _duration, _guid, _callback);
	_callback = null;
	return this;
};

Gimme.ext.fadeTo = function(_startOpacity, _endOpacity, _duration, _guid, _callback, _accLine)
{
	_accLine = _accLine || Gimme.Animation.AccelerationLines.linear;
	g(this.entities).animate(_accLine, _duration, _guid, _callback, doFade, setup);
	
	function setup(_elem)
	{
		// IE will only do opacity for elements that "haveLayout" (whatever).  Anyway, zoom: 1 is a way to trick IE into "havingLayout" which won't visually affect the element
		_elem.style.zoom = '1';
		
		var startOpacity = _startOpacity === 0 ? 0 : _startOpacity || Number(g(_elem).getStyle('opacity'));
		var deltaO = _endOpacity - startOpacity;
		
		return {
		startOpacity: startOpacity,
		deltaO: deltaO };
	}
	
	function doFade(_elem, _pctComplete, _accLine, _args)
	{
		var p = _accLine.getValue(_pctComplete);
		var opacity = _args.startOpacity + p * _args.deltaO;
		g(_elem).setStyle('opacity', opacity);
	}
	
	return this;
};

Gimme.ext.veil = function(_direction, _duration, _guid, _callback, _accLine)
{
	var D = Gimme.Animation.Directions;
	_direction = Math.floor(_direction) || D[_direction] || D.vertically;
	
	g(this.entities).animate(_accLine, _duration, _guid, _callback, doVeil, setup);

	function setup(_elem)
	{
		var display = _elem.style.display || g(_elem).getStyle('display');
		if (display === 'none')
		{
			return false;
		}
	
		var args = (function(_props)
		{
			var a = {};
			g(_props).forEach(function(p, i) { a[p] = parseInt(g(_elem).getStyle(p), 10); });
			return a;
		})(['height', 'width', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft']);
		
		// The snippet above is functionally equivalent to the snippet below,
		// but the above is WAY cooler -- slower, but cooler, and it's not called repeatedly during the animation, only once at the animation start.
		//
		// var height = parseInt(g(_elem).getStyle('height'), 10);
		// var width = parseInt(g(_elem).getStyle('width'), 10);
		// var paddingTop = parseInt(g(_elem).getStyle('paddingTop'), 10);
		// var paddingBottom = parseInt(g(_elem).getStyle('paddingBottom'), 10);
		// var paddingRight = parseInt(g(_elem).getStyle('paddingRight'), 10);
		// var paddingLeft = parseInt(g(_elem).getStyle('paddingLeft'), 10);
		
		// Save important values in an expando so they can be used for unveil (if needed)
		_elem['_$gimme$_veil'] = args.height + ';' + args.width + ';' + args.paddingTop + ';' + args.paddingRight + ';' + args.paddingBottom + ';' + args.paddingLeft;
		
		// Make sure content isnt' bleeding over its container during the veiling process
		_elem.style.overflow = 'hidden';
		
		if ((_direction & D.horizontally) === D.horizontally)
		{
			_elem.style.height = args.height + 'px';
		}
		
		return args;
	}
	
	function doVeil(_elem, _pctComplete, _accLine, _args)
	{
		if (_pctComplete >= 1)
		{
			_elem.style.display = 'none';
		}

		var p = _accLine.getValue(_pctComplete);
	
		var  height, width, paddingTop, paddingRight, paddingBottom, paddingLeft;
		
		if ((_direction & D.vertically) === D.vertically)
		{
			height = _args.height - (p * _args.height);
			paddingTop = _args.paddingTop - (p * _args.paddingTop);
			paddingBottom = _args.paddingBottom - (p * _args.paddingBottom);
			_elem.style.height = height + 'px';
			_elem.style.paddingTop = paddingTop + 'px';
			_elem.style.paddingBottom = paddingBottom + 'px';
		}
		if ((_direction & D.horizontally) === D.horizontally)
		{
			width = _args.width - (p * _args.width);
			paddingRight = _args.paddingRight - (p * _args.paddingRight);
			paddingLeft = _args.paddingLeft  - (p * _args.paddingLeft);
			_elem.style.width = width + 'px';
			_elem.style.paddingRight = paddingRight + 'px';
			_elem.style.paddingLeft = paddingLeft + 'px';			
		}
	}

	return this;
};

Gimme.ext.unveil = function(_direction, _duration, _guid, _callback, _accLine)
{
	var D = Gimme.Animation.Directions;
	_direction = Math.floor(_direction) || D[_direction] || D.vertically;

	var firstRun = true;	
	g(this.entities).animate(_accLine, _duration, _guid, _callback, doUnveil, setup);

	function setup(_elem)
	{
		var display = _elem.style.display || g(_elem).getStyle('display');
		// Safari 2 will return a null CSSStyleDeclaration object if the element is not currently displayed on the screen
		// Thus, the need for the explicit, display !== null check
		if (display !== 'none' && display !== null)
		{
			return false;
		}
		
		// shortcut to this helper function
		var ctp = Gimme.Helper.convertToPixels;
		
		// clone for measurement purposes
		var clone = _elem.cloneNode(true);
		clone.setAttribute('style','position:absolute;top:0;left:0;visibility:hidden;margin:0;padding:0;border:0;height:;width:;');
		clone.style.display = 'block';
		_elem.parentNode.appendChild(clone);
	
		var height, width, paddingTop, paddingRight, paddingBottom, paddingLeft;
		var expando = _elem['_$gimme$_veil'];
		if (expando)
		{
			expando = expando.split(';');
			height = expando[0];
			width = expando[1];
			paddingTop = expando[2];
			paddingRight = expando[3];
			paddingBottom = expando[4];
			paddingLeft = expando[5];
		}
		else
		{
			var gClone = g(clone);
			height = parseInt(ctp(_elem.style.height), 10) || parseInt(gClone.getStyle('height'), 10);
			width = parseInt(ctp(_elem.style.width), 10) || parseInt(gClone.getStyle('width'), 10);
			
			clone.style.padding = '';
			paddingTop = parseInt(gClone.getStyle('paddingTop'), 10);
			paddingBottom = parseInt(gClone.getStyle('paddingBottom'), 10);
			paddingRight = parseInt(gClone.getStyle('paddingRight'), 10);
			paddingLeft = parseInt(gClone.getStyle('paddingLeft'), 10);
		}
		
		_elem.parentNode.removeChild(clone);
		_elem.style.overflow = 'hidden';

		return {
		deltaH: height,
		deltaW: width,
		paddingTop: paddingTop,
		paddingBottom: paddingBottom,
		paddingLeft: paddingLeft,
		paddingRight: paddingRight };
	}
	
	function doUnveil(_elem, _pctComplete, _accLine, _args)
	{
		var height, width, paddingTop, paddingBottom, paddingRight, paddingLeft;
		var gElem = g(_elem);
		var p = 1 - _accLine.getValue(_pctComplete);
		
		if (firstRun)
		{
			_elem.style.display = 'block';
			firstRun = false;
		}
		
		if (_pctComplete >= 1)
		{
			_elem.style.overflow = '';			
		}
		
		if (_direction === D.vertically)
		{
			gElem.setStyles(
				'width', _args.deltaW + 'px',
				'paddingRight', _args.paddingRight + 'px',
				'paddingLeft', _args.paddingLeft + 'px');
		}
		else if (_direction === D.horizontally)
		{
			gElem.setStyles(
				'height', _args.deltaH + 'px',
				'paddingTop', _args.paddingTop + 'px',
				'paddingBottom', _args.paddingBottom + 'px');
		}
		
		if ((_direction & D.vertically) === D.vertically)
		{
			height = _args.deltaH - (p * _args.deltaH);
			paddingTop = _args.paddingTop - (p * _args.paddingTop);
			paddingBottom = _args.paddingBottom - (p * _args.paddingBottom);
			gElem.setStyles(
				'height', height + 'px',
				'paddingTop', paddingTop + 'px',
				'paddingBottom', paddingBottom + 'px');
		}
		if ((_direction & D.horizontally) === D.horizontally)
		{				
			width = _args.deltaW - (p * _args.deltaW);
			paddingRight = _args.paddingRight - (p * _args.paddingRight);
			paddingLeft = _args.paddingLeft  - (p * _args.paddingLeft);
			gElem.setStyles(
				'width', width + 'px',
				'paddingRight', paddingRight + 'px',
				'paddingLeft', paddingLeft + 'px');
		}
	}
	
	return this;
};

Gimme.ext.scrollTo = function (_duration, _guid, _callback, _accLine)
{
	g(this.entities[0]).animate(_accLine, _duration, _guid, _callback, doScroll, setup);

	function setup(_elem)
	{	
		var scrollPos = Gimme.Screen.getScrollPosition();
		var deltaY = g(_elem).getPagePosition().y - scrollPos.y;
		
		return {
		scrollPos: scrollPos,
		deltaY: deltaY };
	}
	
	function doScroll(_elem, _pctComplete, _accLine, _args)
	{
		var p = _accLine.getValue(_pctComplete);
		
		var y = _args.scrollPos.y + p * _args.deltaY;
		window.scrollTo(0, Math.floor(y));	
	}
	
	return this;
};

Gimme.ext.slideToPoint = function(_endPt, _duration, _guid, _callback, _accLine)
{
	g(this.entities).animate(_accLine, _duration, _guid, _callback, doSlide, setup);	

	function setup(_elem)
	{
		var startPt = g(_elem).getComputedPosition();
		
		if (_endPt.x === null) { _endPt.x = startPt.x; }
		if (_endPt.y === null) { _endPt.y = startPt.y; }
		
		var deltaY = _endPt.y - startPt.y;
		var deltaX = _endPt.x - startPt.x;
		
		return {
		startPt: startPt,
		deltaX: deltaX,
		deltaY: deltaY };
	}
	
	function doSlide(_elem, _pctComplete, _accLine, _args)
	{
		var startPt = _args.startPt;
		var deltaX = _args.deltaX;
		var deltaY = _args.deltaY;
	
		var p = _accLine.getValue(_pctComplete);

		var x = startPt.x + p * deltaX;
		var y = startPt.y + p * deltaY;
		
		_elem.style.top = Math.floor(Math.round(y)) + 'px';
		_elem.style.left = Math.floor(Math.round(x)) + 'px';		
	}
	
	return this;
};

Gimme.ext.followPath = function(_path, _factor, _duration, _guid, _callback)
{
	_factor = _factor || 1;
	g(this.entities).animate(_path, _duration, _guid, _callback, doFollowPath, setup);
	
	function setup(_elem)
	{
		var startPt = g(_elem).getComputedPosition();
		
		return {
		startPt: startPt };
	}
	
	function doFollowPath(_elem, _pctComplete, _path, _args)
	{
		var startPt = _args.startPt;
		
		var p = _path.getPoint(_pctComplete);
		
		var x = startPt.x + p.x * _factor;
		var y = startPt.y + p.y * _factor;
			
		_elem.style.top = Math.floor(Math.round(y)) + 'px';
		_elem.style.left = Math.floor(Math.round(x)) + 'px';
	}
	
	return this;
};

Gimme.ext.animate = function(_path, _duration, _guid, _callback, _logicFn, _setupFn)
{
	// abort the animation request if there are no DOM elements to animate
	var numElems = this.entities.length;
	if (numElems < 1)
	{
		return;
	}	
	
	// use the defaultCurve is none is specified
	_path = _path || Gimme.Animation.AccelerationLines.defaultLine;
	
	// determine the duration of the animation (either user specified via keyword or number, or default to 'quickly')
	_duration = Math.floor(_duration) || Gimme.Animation.Speeds[_duration] || Gimme.Animation.Speeds.quickly;
	
	// auto generate a GUID for this animation if one wasn't specified
	_guid = _guid || 'AUTOGUID_' + Math.random(new Date().getTime());
	
	// if an animation marked by the given GUID is already running (exists in the hash) don't try to start it again
	if (!Gimme.Animation.isGrouping() && Gimme.Animation.isRunning(_guid))
	{
		return;
	}
	
	Gimme.Animation.whenDone(_guid, _callback);
	
	var numFinished = 0;
	var startTime = new Date().getTime();
	
	this.forEach(function(_elem, _index)
	{
		Gimme.Animation.start(_guid, doAnimate(_elem, _index), 1);
	});
	
	function doAnimate(_el, _i)
	{
		var args = _setupFn(_el);
		
		var worker = function()
		{
			var t = new Date().getTime();
			var pct = (t - startTime) / _duration;
			
			if (pct >= 1)
			{
				if (typeof _logicFn === 'function')
				{
					_logicFn(_el, 1, _path, args);
				}
				
				if (++numFinished === numElems)
				{
					Gimme.Animation.end(_guid);
					_setupFn = null;
					_logicFn = null;
				}
			}
			else
			{
				_logicFn(_el, pct, _path, args);			
			}
		};
		
		var deadAnimation = function()
		{
			Gimme.Animation.end(_guid);
		};

		return args ? worker : deadAnimation;
	}	
};

Gimme.ver='Gimme v2.0.0.3 (Caspian) :: 12/2/2008, 9:57:45';