//HASH


var Hashtable = (function() {
	var prefix = ' $';
	function isNull(object) {return !object && ('object' == typeof object);}
	function isNumber(object) {return 'number' == typeof object;}
	function isUndefined(object) {return 'undefined' == typeof object;}
	function Element(key, value) {
		this.key = key;
		this.value = value;
		this.next = null;
	}
	function Hashtable() {
		var table = {data : []},
		    entries = 0;

		this.get = function(key) {var index, tableData = table.data;
			if(!isNull(key) && !isUndefined(key)
			 && isNumber(index = table[prefix + key]))
			{
				var node = tableData[index];
				while(node && (node.key !== key)) {node = node.next;}
				if(node) {return node.value;}
			}
		};
		this.put = function(key, value) {var index, prefixedKey, tableData = table.data;
			if(isNull(key) || isUndefined(key) || isUndefined(value)) {return;}
			if(isNumber(index = table[prefixedKey = prefix + key])) {
				var node = tableData[index], previousNode = null;
				while(node && (node.key !== key)) {
					previousNode = node;
					node = node.next;
				}
				if(node) {
					var oldValue = node.value;
					node.value = value;
					return oldValue;
				} else {
					previousNode.next = new Element(key, value);
				}
			}
			else {
				table[prefixedKey] = tableData.length;
				tableData[tableData.length] = new Element(key, value);
			}
			++entries;
		};
		this.remove = function(key) {var index, prefixedKey, tableData = table.data;
			if(!isNull(key) && !isUndefined(key)
			 && isNumber(index = table[prefixedKey = prefix + key]))
			{
				var node = tableData[index], previousNode = null, returnValue;
				while(node && (node.key !== key)) {
					previousNode = node;
					node = node.next;
				}
				if(!node) {return;}
				returnValue = node.value;
				if(previousNode) {previousNode.next = node.next;}
				else {tableData[index] = node.next;}
				if(!tableData[index]) {
					for(var length = tableData.length - 1; index < length; ++index) {
						tableData[index] = tableData[index + 1];
						--table[prefix + tableData[index].key];
					}
					delete tableData[index];
					delete table[prefixedKey];
				}
				--entries;
				return returnValue;
			}
		};
	}
	Hashtable.prototype.containsKey = function(key) {
		return !isUndefined(this.get(key));
	};
	return Hashtable;
})();




//FADE

var fade = (function() {
	var global = this,
	    objects = new Hashtable(),
	    /* If it's determined that the host supports the application of an
	     * opacity value either through opacity, or a proprietary property, this
	     * variable will hold that property name.
	     *
	     * This should be left undefined. If it's not set when setOpacity is
	     * called, hosts that implement DOM 2 Style, but not opacity, won't
	     * attempt to modify the value.
	     */
	    property;

	function getOpacity(object) {var style;
		if(object.filters && object.filters['DXImageTransform.Microsoft.Alpha']) {
			getOpacity = function(object) {
				return object.filters['DXImageTransform.Microsoft.Alpha'].Opacity;
			};
		} else if(global.getComputedStyle
		 && (style = global.getComputedStyle(object, null))
		 && style.getPropertyValue
		 && (style.getPropertyValue(property = 'opacity')
		 || style.getPropertyValue(property = '-moz-opacity')
		 || style.getPropertyValue(property = '=khtml-opacity')))
		{
			getOpacity = function(object) {
				return parseFloat(
					global.getComputedStyle(object, null).getPropertyValue(property)
				) * 100;
			};
			style = null;
		} else {
			getOpacity = function() {return 100;};
		}
		return getOpacity(object);
	}

	function setOpacity(object, opacity) {
		if(object.filters && object.filters['DXImageTransform.Microsoft.Alpha']) {
			setOpacity = function(object, opacity) {
				object.filters['DXImageTransform.Microsoft.Alpha'].Opacity = opacity;
			};
		/* If the property variable hasn't been set, an earlier call to getOpacity
		 * determined that CSS opacity values aren't supported by the host.
		 */
		} else if(property && object.style && object.style.setProperty) {
			setOpacity = function(object, opacity) {
				object.style.setProperty(property, opacity / 100, '');
			};
		} else {
			setOpacity = function() {};
		}
		setOpacity(object, opacity);
	}

	return function(object, targetOpacity, rate, delta) {
		var opacity = getOpacity(object);

		function stepFade() {var onTarget;
			opacity += delta;
			onTarget = (0 < delta) ?
				opacity >= targetOpacity
			: opacity <= targetOpacity;
			if(onTarget) {
				opacity = targetOpacity;
				objects.remove(object);
			}
			setOpacity(object, opacity);
			if(!onTarget) {objects.put(object, setTimeout(stepFade, rate));}
		}
		/* This function relies on the closure produced by the inner function
		 * above. Though the closure could be preserved by exposing the inner
		 * function publicly, it would cause clashes should the effect be active
		 * on more than one element simultaneously. As such, any host that is
		 * unable to accept a function argument to the setTimeout method will
		 * silently fail.
		 */
		stepFade.toString = function() {return ';';};

		/* If the element is already fading, stop the effect. */
		if(objects.containsKey(object)) {clearTimeout(objects.get(object));}
		/* This line prevents a bug in Gecko-based browsers where artifacts
		 * appear when the opacity reaches 100%.
		 */
		if(100 == targetOpacity) {--targetOpacity;}
		if(opacity > targetOpacity) {delta = -delta;}
		if(delta && rate) {objects.put(object, setTimeout(stepFade, rate));}
	}
})();
