/*
 * Glow JavaScript Library
 * Copyright (c) 2008 British Broadcasting Corporation
 */
/*@cc_on @*//*@if (@_jscript_version > 5.1)@*/;/*
Namespace: glow.tweens
*/
glow.module("glow.tweens", "0.4.0", {
	require: [],
	implementation: function() {
		
		/*
		PrivateMethod: _reverse
			Takes a tween function and returns a function which does the reverse
		*/
		function _reverse(tween) {
			return function(t) {
				return 1 - tween(1 - t);
			}
		}
		
		return {
			/*
			Method: linear
				Returns linear tween. Will transition values from start to finish with no acceleration or deceleration.
			*/
			linear: function() {
				return function(t) { return t; };
			},
			/*
			Method: easeIn
				Creates a tween which starts off slowly and accelerates 
				
			Arguments:
				strength - (optional) how strong the easing is. A higher number means the animation starts off slower and ends quicker. Default is 2
			*/
			easeIn: function(strength) {
				strength = strength || 2;
				return function(t) {
					return Math.pow(1, strength - 1) * Math.pow(t, strength);
				}
			},
			/*
			Method: easeOut
				Creates a tween which starts off fast and decelerates 
				
			Arguments:
				strength - (optional) how strong the easing is. A higher number means the animation starts off faster and ends slower. Default is 2
			*/
			easeOut: function(strength) {
				return _reverse(this.easeIn(strength));
			},
			/*
			Method: easeBoth
				Creates a tween which starts off slowly, accelerates then decelerates after the half way point. This produces a smooth and natural looking transition.
				
			Arguments:
				strength - (optional) how strong the easing is. A higher number produces a greater difference between start / end speed and the mid speed. Default is 2
			*/
			easeBoth: function(strength) {
				return this.combine(this.easeIn(strength), this.easeOut(strength));
			},
			/*
			Method: overshootIn
				Returns the reverse of <overshootOut>
				
			Arguments:
				amount - (optional) how much to overshoot. The default is 1.70158 which results in a 10% overshoot.
			*/
			overshootIn: function(amount) {
				return _reverse(this.overshootOut(amount));
			},
			/*
			Method: overshootOut
				Creates a tween which overshoots its end point then returns to its end point.
				
			Arguments:
				amount - (optional) how much to overshoot. The default is 1.70158 which results in a 10% overshoot.
			*/
			overshootOut: function(amount) {
				amount = amount || 1.70158;
				return function(t) {
					if (t == 0 || t == 1) { return t; }
					return ((t -= 1)* t * ((amount + 1) * t + amount) + 1);
				}
			},
			/*
			Method: overshootBoth
				Returns a combination of <overshootIn> and <overshootOut>
				
			Arguments:
				amount - (optional) how much to overshoot. The default is 1.70158 which results in a 10% overshoot.
			*/
			overshootBoth: function(amount) {
				return this.combine(this.overshootIn(amount), this.overshootOut(amount));
			},
			/*
			Method: bounceIn
				Returns the reverse of <bounceOut>
			*/
			bounceIn: function() {
				return _reverse(this.bounceOut());
			},
			/*
			Method: bounceOut
				Returns a tween which bounces against the final value 3 times
				before stopping
			*/
			bounceOut: function() {
				return function(t) {
					if (t < (1 / 2.75)) {
						return 7.5625 * t * t;
					} else if (t < (2 / 2.75)) {
						return (7.5625 * (t -= (1.5 / 2.75)) * t + .75);
					} else if (t < (2.5 / 2.75)) {
						return (7.5625 * (t -= (2.25 / 2.75)) * t + .9375);
					} else {
						return (7.5625 * (t -= (2.625 / 2.75)) * t + .984375);
					}
				};
			},
			/*
			Method: bounceBoth
				Returns a combination of <bounceIn> and <bounceOut>
			*/
			bounceBoth: function() {
				return this.combine(this.bounceIn(), this.bounceOut());
			},
			/*
			Method: elasticIn
				Returns the reverse of <elasticOut>.
				
			Arguments:
				amplitude - (optional) how strong the elasticity is. Default is 1
				period - (optional) the frequency period. Default is 0.3
			*/
			elasticIn: function(a, p) {
				return _reverse(this.elasticOut(a, p));
			},
			/*
			Method: elasticOut
				Creates a tween which has an elastic movement. You can tweak the tween using the
				parameters but you'll probably find the defaults sufficient. 
				
			Arguments:
				amplitude - (optional) how strong the elasticity is. Default is 1
				period - (optional) the frequency period. Default is 0.3
			*/
			elasticOut: function(a, p) {
				return function (t) {
					if (t == 0 || t == 1) {
						return t;
					}
					if (!p) {
						p = 0.3;
					}
					if (!a || a < 1) {
						a = 1;
						var s = p / 4;
					} else {
						var s = p / (2 * Math.PI) * Math.asin(1 / a);
					}
					return a * Math.pow(2, -10 * t) * Math.sin( (t-s) * (2 * Math.PI) / p) + 1;
				}
			},
			/*
			Method: elasticBoth
				Returns a combination of <elasticIn> and <elasticOut>
				
			Arguments:
				amplitude - (optional) how strong the elasticity is. Default is 1
				period - (optional) the frequency period. Default is 0.45
			*/
			elasticBoth: function(a, p) {
				p = p || 0.45;				
				return this.combine(this.elasticIn(a, p), this.elasticOut(a, p));
			},
			/*
			Method: combine
				Create a tween from two tweens. This can be useful to make custom tweens which,
				for example, start with an easeIn and end with an overshootOut. To keep the motion
				natural, you should configure your tweens so the first ends and the same velocity
				that the second starts.
				
			Arguments:
				tweenIn - Tween to use for the first half
				tweenOut - Tween to use for the second half
				
			Example:
				(code)
				var myTween = glow.tweens.combine(
					glow.tweens.easeIn(4.5),
					glow.tweens.overshootOut()
				);
				(end)
				4.5 has been chosen for the easeIn strength so it ends at the same velocity
				as overshootOut starts.
			*/
			combine: function(tweenIn, tweenOut) {
				return function (t) {
					if (t < 0.5) {
						return tweenIn(t * 2) / 2;
					} else {
						return tweenOut((t - 0.5) * 2) / 2 + 0.5;
					}
				}
			}
		};
	}
});
;/*
Namespace: glow.anim
*/
glow.module("glow.anim", "0.4.0", {
	require: ["glow.tweens", "glow.events", "glow.dom"],
	implementation: function() {
		//private
		var manager,
			events = glow.events,
			dom = glow.dom,
			get = dom.get,
			debug = glow.debug,
			hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/,
			noNegatives = /width|height|padding|opacity/,
			usesYAxis = /height|top/,
			getUnit = /[\d\.]+(\D+)/,
			testElement = dom.create('<div style="position:absolute;visibility:hidden"></div>');
		/*
		PrivateNamespace: manager
			Singleton which handles the queueing and playing of animations
		*/
		(function() {
			var queue = [], //running animations
				queueLen = 0,
				intervalTime = 1, //ms between intervals
				interval; //holds the number for the interval
			manager = {
				/*
				PrivateMethod: addToQueue
					Adds an animation to the queue
				*/
				addToQueue: function(anim) {
					//add the item to the queue
					queue[queueLen++] = anim;
					anim._playing = true;
					anim._timeAnchor = anim._timeAnchor || new Date().valueOf();
					if (!interval) {
						this.startInterval();
					}
				},
				/*
				PrivateMethod: removeFromQueue
					Removes an animation from the queue
				*/
				removeFromQueue: function(anim) {
					for (var i = 0; i < queueLen; i++) {
						if (queue[i] == anim) {
							queue.splice(i, 1);
							anim._timeAnchor = null;
							anim._playing = false;
							//stop the queue if there's nothing in it anymore
							if (--queueLen == 0) {
								this.stopInterval();
							}
							return;
						}
					}
				},
				/*
				PrivateMethod: startInterval
					Start processing the queue every interval
				*/
				startInterval: function() {
					interval = window.setInterval(this.processQueue, intervalTime);
				},
				/*
				PrivateMethod: stopInterval
					Stop processing the queue
				*/
				stopInterval: function() {
					window.clearInterval(interval);
					interval = null;
				},
				/*
				PrivateMethod: processQueue
					Animate each animation in the queue
				*/
				processQueue: function() {
					var anim, i, now = new Date().valueOf();
					for (i = 0; i < queueLen; i++) {
						anim = queue[i];
						if (anim.position == anim.duration) {
							manager.removeFromQueue(anim);
							//need to decrement the index because we've just removed an item from the queue
							i--;
							events.fire(anim, "complete");
							continue;
						}
						if (anim.useSeconds) {
							anim.position = (now - anim._timeAnchor) / 1000;
							if (anim.position > anim.duration) {
								anim.position = anim.duration;
							}
						} else {
							anim.position++;
						}
						anim.value = anim.tween(anim.position / anim.duration);
						events.fire(anim, "frame");
					}
				}
			};
		})();
		
		/*
		PrivateMethod: convertCssUnit
			Converts a css unit
			
		Arguments:
			element - nodelist
			fromValue - string / int (assumed pixels)
			toUnit - string (em|%|pt...)
			axis - string (x|y)
			
			we need to know the axis for calculating relative values, since they're
			relative to the width / height of the parent element depending
			on the situation
		*/
		function convertCssUnit(element, fromValue, toUnit, axis) {
			var elmStyle = testElement[0].style,
				axisProp = (axis == "x") ? "width" : "height",
				startPixelValue,
				toUnitPixelValue;
			//reset stuff that may affect the width / height
			elmStyle.margin = elmStyle.padding = elmStyle.border = "0";
			startPixelValue = testElement.css(axisProp, fromValue).insertAfter(element)[axisProp]();
			//using 10 of the unit then dividing by 10 to increase accuracy
			toUnitPixelValue = testElement.css(axisProp, 10 + toUnit)[axisProp]() / 10;
			testElement.remove();
			return startPixelValue / toUnitPixelValue;
		}
		
		/*
		PrivateMethod: keepWithinRange
			Takes a number then an (optional) lower range and an (optional) upper range. If the number
			is outside the range the nearest range boundary is returned, else the number is returned
		*/
		function keepWithinRange(num, start, end) {
			if (start !== undefined && num < start) {
				return start;
			}
			if (end !== undefined && num > end) {
				return end;
			}
			return num;
		}
		
		/*
		PrivateMethod: buildAnimFunction
			Builds a function for an animation
		*/
		function buildAnimFunction(element, spec) {
			var cssProp,
				r = ["a=(function(){"],
				rLen = 1,
				fromUnit,
				unitDefault = [0,"px"],
				to,
				from,
				unit,
				a;
			
			for (cssProp in spec) {
				r[rLen++] = 'element.css("' + cssProp + '", ';
				//fill in the blanks
				to = spec[cssProp].to;
				if ((from = spec[cssProp].from) === undefined) {
					if (cssProp == "font-size" || cssProp == "background-position") {
						throw new Error("From value must be set for " + cssProp);
					}
					from = element.css(cssProp);
				}
				//TODO help some multi value things?
				if (hasUnits.test(cssProp)) {
					//normalise the units for unit-ed values
					unit = (getUnit.exec(spec[cssProp].to) || unitDefault)[1];
					fromUnit = (getUnit.exec(from) || unitDefault)[1];
					//make them numbers, we have the units seperate
					from = parseFloat(from) || 0;
					to = parseFloat(to) || 0;
					//if the units don't match, we need to have a play
					if (from && unit != fromUnit) {
						if (cssProp == "font-size") {
							throw new Error("Units must be the same for font-size");
						}
						from = convertCssUnit(element, from + fromUnit, unit, usesYAxis.test(cssProp) ? "y" : "x");
					}
					if (noNegatives.test(cssProp)) {
						r[rLen++] = 'keepWithinRange((' + (to - from) + ' * this.value) + ' + from + ', 0) + "' + unit + '"';
					} else {
						r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from + ' + "' + unit + '"';
					}
				} else if (! (isNaN(from) || isNaN(to))) { //both pure numbers
					from = Number(from);
					to = Number(to);
					r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from;
				} else if (cssProp.indexOf("color") != -1) {
					to = dom.parseCssColor(spec[cssProp].to);
					if (! glow.lang.hasOwnProperty(from, "r")) {
						from = dom.parseCssColor(from);
					}
					r[rLen++] = '"rgb(" + keepWithinRange(Math.round(' + (to.r - from.r) + ' * this.value + ' + from.r +
						'), 0, 255) + "," + keepWithinRange(Math.round(' + (to.g - from.g) + ' * this.value + ' + from.g +
						'), 0, 255) + "," + keepWithinRange(Math.round(' + (to.b - from.b) + ' * this.value + ' + from.b +
						'), 0, 255) + ")"';
				} else if (cssProp == "background-position") {
					var vals = {},
						fromTo = ["from", "to"],
						unit = (getUnit.exec(from) || unitDefault)[1];
					vals.fromOrig = from.toString().split(/\s/);
					vals.toOrig = to.toString().split(/\s/);
					
					if (vals.fromOrig[1] === undefined) {
						vals.fromOrig[1] = "50%";
					}
					if (vals.toOrig[1] === undefined) {
						vals.toOrig[1] = "50%";
					}
					
					for (var i = 0; i < 2; i++) {
						vals[fromTo[i] + "X"] = parseFloat(vals[fromTo[i] + "Orig"][0]);
						vals[fromTo[i] + "Y"] = parseFloat(vals[fromTo[i] + "Orig"][1]);
						vals[fromTo[i] + "XUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][0]) || unitDefault)[1];
						vals[fromTo[i] + "YUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][1]) || unitDefault)[1];
					}
					
					if ((vals.fromXUnit !== vals.toXUnit) || (vals.fromYUnit !== vals.toYUnit)) {
						throw new Error("Mismatched axis units cannot be used for " + cssProp);
					}
					
					r[rLen++] = '(' + (vals.toX - vals.fromX) + ' * this.value + ' + vals.fromX + ') + "' + vals.fromXUnit + ' " + (' +
								(vals.toY - vals.fromY) + ' * this.value + ' + vals.fromY + ') + "' + vals.fromYUnit + '"';
				}
				r[rLen++] = ');';
			}
			r[rLen++] = "})";
			return eval(r.join(""));
		}
		
		//public
		var r = {}; //return object
		
		/*
		Method: css
			Creates an animation which animates css properties of an element
			
		Arguments:
			*element* (selector/nodeList/Element)
			
			Element to animate. This can be a css selector (first match will be used), glow.dom.NodeList (first node will be used), or a dom element.

			*duration* (int)
			
			Animation duration, in seconds by default
			
			*animationSpec* (object)
			
			An object describing the properties to animate (see below)
			
			*opts* (object)
			
			Optional options object
			
			animationSpec is a hash of css properties and to & from values.
			
			(code)
			//an example of an animationSpec object
			{
				"height": {from: "10px", to: "100px"},
				"width": {to: "100px"},
				"font-size": {from: "0.5em", to: "1.3em"}
			}
			(end)
			
			'from' may be omited if the current value can be automatically detected. Values can
			be auto-detected if they've been set in a stylesheet.
			
			Options:
			
			opts may contain any of the following :
			
			*useSeconds* (boolean)
			
			Is the duration in seconds rather than frames? Default is true
			
			Default - True. i.e duration in seconds by default.
			
			*tween* (function)
			
			The way the value moves through time. See <glow.tweens>.
			
			Default - linear tween

		Example:
			(code)
			glow.anim.css("#myElement", 1, {
				"height" : {to:0},
				"opacity" : {to:0}
			}).start();
			(end)
		
		Returns:
			<glow.anim.Animation>
		*/		
		r.css = function(element, duration, spec, opts) {
			element = get(element);
			/*&debug*/
				debug.assert(!!element[0], "glow.anim.css - element not found");
			/*&enddebug*/
			var anim = new r.Animation(duration, opts),
				cssProp;
			
			events.addListener(anim, "frame", buildAnimFunction(element, spec));
			return anim;
		};
		
		/*
		Class: glow.anim.Animation
			Defines a value which changes over time. This is the low-level way
			of creating an animation. If you're wanting to animate an element's CSS
			properties, see <glow.anim.css>.
		
		Constructor:
			*duration* (int)
			
			Length of the animation in seconds / frames
			
			*opts* (object)
			
			Object of options
			
			Options:
			
			opts may contain any of the following :
			
			*useSeconds* (Boolean)
			
			Is the duration in seconds rather than frames? Default is true
			
			*tween* (Function)
			
			The way the value moves through time. See <glow.tweens>. Default is a linear tween
			
			Animations which are given a duration in seconds may drop frames to finish in the given time.
			
			Once you have created your animation instance, you can listen to events such as "frame"
			which is fired on every frame.
			
			(code)
			var myAnim = new glow.anim.Animation(5, {tween:glow.tweens.easeBoth()});
			(end)
		*/
		r.Animation = function(duration, opts) {
			opts = glow.lang.apply({
				useSeconds: true,
				tween: glow.tweens.linear()
			}, opts);
			/*
			PrivateProperty: _queueIndex
				Boolean. Is the animation playing?
			*/
			this._playing = false;
			/*
			PrivateProperty: _timeAnchor
				Number. A timestamp used to keep the animation in the right position
			*/
			this._timeAnchor = null;
			/*
			Property: duration
				Number. Length of the animation in seconds / frames
			*/
			this.duration = duration;
			/*
			Property: useSeconds
				Boolean. Is the duration in seconds rather than frames?
			*/
			this.useSeconds = opts.useSeconds;
			/*
			Property: tween
				Function. The tween used by the animation
			*/
			this.tween = opts.tween;
			/*
			Property: position
				Number. Seconds since starting, or current frame
			*/
			this.position = 0;
			/*
			Property: value
				Number. Current tweened value of the animtion, usually between 0 & 1. Begins at 0
				at the start of the animation and ends at 1.
			*/
			this.value = 0;
	
		};
		r.Animation.prototype = {
			/*
			Method: start
				Starts playing the animation from the beginning

			Example:
				(code)
				var myAnim = new glow.anim.Animation(5, {tween:glow.tweens.easeBoth()});
				//attach events here
				myAnim.start();
				(end)
			
			Returns:
				<glow.anim.Animation>
			*/
			start: function() {
				if (this._playing) {
					this.stop();
				}
				var e = events.fire(this, "start");
				if (e.defaultPrevented()) { return this; }
				this.position = 0;
				manager.addToQueue(this);
				
				return this;
			},
			/*
			Method: stop
				Stops the animation playing
			
			Returns:
				<glow.anim.Animation>
			*/
			stop: function() {
				if (this._playing) {
					var e = events.fire(this, "stop");
					if (e.defaultPrevented()) { return this; }
					manager.removeFromQueue(this);
				}
				return this;
			},
			/*
			Method: resume
				Resume the animation from where it was stopped
			
			Returns:
				<glow.anim.Animation>
			*/
			resume: function() {
				if (! this._playing) {
					var e = events.fire(this, "resume");
					if (e.defaultPrevented()) { return this; }
					//set the start time to cater for the pause
					this._timeAnchor = new Date().valueOf() - (this.position * 1000);
					manager.addToQueue(this);
				}
				return this;
			},
			/*
			Method: isPlaying
				Returns true if the animation is playing
			
			Returns:
				Boolean
			*/
			isPlaying: function() {
				return this._playing;
			},
			/*
			Method: goTo
				Go to a specific point in the animation.
			
			Arguments:
				*position* (int)
				
				Position in the animation to go to, this should be in the same units as the duration of your animation (seconds or frames)
			
			Returns:
				<glow.anim.Animation>
			
			Example:
				(code)
				var myAnim = new glow.anim.Animation(5, {tween:glow.tweens.easeBoth()});
				//attach events here
				//start the animation from half way through
				myAnim.goTo(2.5).resume();
				(end)
			*/
			goTo: function(pos) {
				this._timeAnchor = new Date().valueOf() - ((this.position = pos) * 1000);
				this.value = this.tween(this.duration && this.position / this.duration);
				events.fire(this, "frame");
				return this;
			}
			/*
			Event: start
				Fired when the animation is started from the beginning. If your listener
				prevents the default action (for instance, by returning false) the animtion
				will not be started.
			
			Example:
				(code)
				var myAnim = new glow.anim.Animation(5, {tween:glow.tweens.easeBoth()});
				glow.events.addListener(myAnim, "start", function() {
					alert("Started animation which lasts " + this.duration + " seconds");
				});
				myAnim.start();
				(end)
			*/
			/*
			Event: frame
				Fired in each frame of the animation. This is where you'll specify what your animation does.
			
			Example:
				This example would be much easier with <glow.anim.css>, but you can do
				animations this way if you require more control than you get with <glow.anim.css>
			
				(code)
				var myAnim = new glow.anim.Animation(5, {tween:glow.tweens.easeBoth()}),
					myDiv = glow.dom.get("#myDiv"),
					divStartHeight = myDiv.height(),
					divEndHeight = 500,
					divHeightChange = divEndHeight - divStartHeight;
					
				glow.events.addListener(myAnim, "frame", function() {
					myDiv.height(divStartHeight + (divHeightChange * this.value));
				});
				myAnim.start();
				(end)
			*/
			/*
			Event: stop
				Fired when the animation is stopped before its end. If your listener
				prevents the default action (for instance, by returning false) the animtion
				will not be stopped.
			*/
			/*
			Event: complete
				Fired when the animation ends
			*/
			/*
			Event: resume
				Fired when the animation resumes after being stopped. If your listener
				prevents the default action (for instance, by returning false) the animtion
				will not be resumed.
			*/
		};
		/*
		Class: glow.anim.Timeline
		
		Constructor:
		
			*channels* (instance/array) 
			
			an array of channels or a single channel
			
			*opts* (object)
			
			An object of options
			
			A channel is defined as an array containing numbers, animations and functions.
			
			Numbers indicate a number of seconds to wait before proceeding to the next item.
			Animations will be played, when the animation is complete the next item is processed.
			Functions will be called, then the next item is processed
			
			Options:
			
			opts may contain any of the following :
			
			*loop* (boolean)
			
			Should the timeline loop rather than end. The "complete" event does not fire for looping animations.
			
			Default - By default loop is false
			
			In the simplest form, a timeline can be used to string multiple animations together:
			
			(code)
			//make our animations
			var moveUp = glow.anim.css(myDiv, {
				"top": {to:"0"}
			});
			var moveDown = glow.anim.css(myDiv, {
				"top": {to:"100px"}
			});
			//string them together
			new glow.anim.Timeline([moveUp, moveDown]).start();
			(end)
			
			If you wanted a one second gap between the animations, the last line would be
			
			(code)
			new glow.anim.Timeline([moveUp, 1, moveDown]).start();
			(end)
			
			You can run animations simutainiously with multiple channels
			
			(code)
			new glow.anim.Timeline([
				[moveDivUp, 1, moveDivDown],
				[moveListDown, 1, moveListUp]
			]).start();
			(end)
		*/
		r.Timeline = function(channels, opts) {
			/*
			PrivateProperty: _channels
				Array of channels
			*/
			//normalise channels so it's always an array of array(s)
			this._channels = (channels[0] && channels[0].push) ? channels : [channels];
			/*
			PrivateProperty: _channelPos
				index of each currently playing animation
			*/
			this._channelPos = [];
			/*
			PrivateProperty: _playing
				Is the timeline playing?
			*/
			this._playing = false;
			/*
			Property: loop
				Should the timeline loop rather than end. The "complete" event does not fire for looping animations.
				This can be set while a timeline is playing.
			*/
			this.loop = !!(opts && opts.loop);
			
			var i, j, iLen, jLen,
				channel,
				allChannels = this._channels,
				totalDuration = 0,
				channelDuration;
			
			//process channels
			for (i = 0, iLen = allChannels.length; i < iLen; i++) {
				channel = allChannels[i];
				channelDuration = 0;
				for (j = 0, jLen = channel.length; j < jLen; j++) {
					//create a blank animation for time waiting
					if (typeof channel[j] == "number") {
						channel[j] = new r.Animation(channel[j]);
					}
					if (channel[j] instanceof r.Animation) {
						if (! channel[j].useSeconds) {
							throw new Error("Timelined animations must be timed in seconds");
						}
						channel[j]._timelineOffset = channelDuration * 1000;
						channelDuration += channel[j].duration;
						channel[j]._channelIndex = i;
					}
				}
				totalDuration = Math.max(channelDuration, totalDuration);
			}
			/*
			PrivateProperty: _controlAnim
				This is used to keep the animation in time
			*/
			this._controlAnim = new r.Animation(totalDuration);
			events.addListener(this._controlAnim, "frame", this._processFrame, this);
			events.addListener(this._controlAnim, "complete", this._complete, this);
		};
		r.Timeline.prototype = {
			/*
			PrivateMethod: _advanceChannel
				Move to the next position in a particular channel
			*/
			_advanceChannel: function(i) {
				//is there a next animation in the channel?
				var currentAnim = this._channels[i][this._channelPos[i]],
					nextAnim = this._channels[i][++this._channelPos[i]];
					
				if (currentAnim && currentAnim._playing) {
					currentAnim._playing = false;
					events.fire(currentAnim, "complete");
				}
				if ((nextAnim) !== undefined) {
					if (typeof nextAnim == "function") {
						nextAnim();
						this._advanceChannel(i);
					} else {
						nextAnim.position = 0;
						nextAnim._channelIndex = i;
						events.fire(nextAnim, "start");
						nextAnim._playing = true;
					}
				}
			},
			_complete: function() {
				if (this.loop) {
					this.start();
					return;
				}
				events.fire(this, "complete");
			},
			_processFrame: function() {
				var i, len, anim, controlAnim = this._controlAnim,
					msFromStart = (new Date().valueOf()) - controlAnim._timeAnchor;
				
				for (i = 0, len = this._channels.length; i < len; i++) {
					if (! (anim = this._channels[i][this._channelPos[i]])) { continue; }
					anim.position = (msFromStart - anim._timelineOffset) / 1000;
					if (anim.position > anim.duration) {
						anim.position = anim.duration;
					}
					anim.value = anim.tween(anim.position / anim.duration);
					events.fire(anim, "frame");
					if (anim.position == anim.duration) {
						this._advanceChannel(i);
					}
				}
			},
			/*
			Method: start
				Starts playing the timeline from the beginning
			*/
			start: function() {
				var e = events.fire(this, "start");
				if (e.defaultPrevented()) { return this; }
				var i, iLen, j, jLen, anim;
				this._playing = true;
				for (i = 0, iLen = this._channels.length; i < iLen; i++) {
					this._channelPos[i] = -1;
					this._advanceChannel(i);
					for (j = this._channels[i].length; j; j--) {
						anim = this._channels[i][j];
						if (anim instanceof r.Animation) {
							anim.goTo(0);
						}
					}
				}
				this._controlAnim.start();
			},
			/*
			Method: stop
				Stops the timeline
			*/
			stop: function() {
				if (this._playing) {
					var e = events.fire(this, "stop");
					if (e.defaultPrevented()) { return this; }
					this._playing = false;
					var anim;
					for (var i = 0, len = this._channels.length; i<len; i++) {
						anim = this._channels[i][this._channelPos[i]];
						if (anim instanceof r.Animation && anim._playing) {
							events.fire(anim, "stop");
							anim._playing = false;
						}
					}
					this._controlAnim.stop();
				}
			},
			/*
			Method: resume
				Resumes the timeline from wherever it was stopped
			*/
			resume: function() {
				if (! this._playing) {
					var e = events.fire(this, "resume");
					if (e.defaultPrevented()) { return this; }
					this._playing = true;
					var anim;
					for (var i = 0, len = this._channels.length; i<len; i++) {
						anim = this._channels[i][this._channelPos[i]];
						if (anim instanceof r.Animation && !anim._playing) {
							events.fire(anim, "resume");
							anim._playing = true;
						}
					}
					this._controlAnim.resume();
				}
			},
			/*
			Method: isPlaying
				Returns true if the timeline is playing
			
			Returns:
				Boolean
			*/
			isPlaying: function() {
				return this._playing;
			}
			/*
			Event: start
				Fired when the timeline is started from the beginning. This event
				will also trigger during each loop of a looping animation. If your listener
				prevents the default action (for instance, by returning false) the timeline
				will not start.
			
			Example:
				(code)
				var myTimeline = new glow.anim.Timeline([anim1, anim2]);
				glow.events.addListener(myTimeline, "start", function() {
					alert("Started timeline");
				});
				myTimeline.start();
				(end)
			*/
			/*
			Event: stop
				Fired when the timeline is stopped before its end. If your listener
				prevents the default action (for instance, by returning false) the timeline
				will not stop.
			*/
			/*
			Event: complete
				Fired when the timeline ends. This event does not fire on looping
				timelines.
			*/
			/*
			Event: resume
				Fired when the timeline resumes after being stopped. If your listener
				prevents the default action (for instance, by returning false) the timeline
				will not resume.
			*/
		};
		return r;
	}
});
;
/*
Namespace: glow.dragdrop
*/
glow.module("glow.dragdrop", "0.4.0", {
	require: ["glow.tweens", "glow.events", "glow.dom", "glow.anim"],
	implementation: function() {
		var events		   = glow.events,
			addListener	   = events.addListener,
			fire		   = events.fire,
			removeListener = events.removeListener,
			dom			   = glow.dom,
			$			   = dom.get,
			create		   = dom.create;

		//public
		var r = {}, 
			_zIndex = 1000,
			_ieStrict = (document.compatMode == "CSS1Compat" && glow.env.ie >= 5) ? true : false,
			_ieTrans= (document.compatMode != "CSS1Compat" && glow.env.ie >= 5) ? true : false,
			_ie = glow.env.ie >= 5;

		/*
		PrivateFunction: memoize(clss, name)

		Replace a method with a version that caches the result after the first run.

		Arguments:

			*clss* (function)

			The class whose method is being memoized.

			*name*

			The name of the method to memoize.
		*/
		
		function memoize (clss, name) {
			var orig = clss.prototype[name];
			var cachedName = 'cached_' + name;
			clss.prototype[name] = function () {
				if (cachedName in this) return this[cachedName];
				return this[cachedName] = orig.apply(this, arguments);
			};
		}

		/*
		PrivateFunction: memoizeNamed(clss, methodName)

		Replace a method that takes a name with a version that caches the result for each name after the first run.

		Arguments:

			*clss* (function)

			The class whose method is being memoized.

			*methodName*

			The name of the method to memoize.
		*/

		function memoizeNamed (clss, methodName) {
			var orig = clss.prototype[methodName];
			var cachedName = 'cached_' + methodName;
			clss.prototype[methodName] = function (name) {
				if (! this[cachedName]) this[cachedName] = {};
				if (name in this[cachedName]) return this[cachedName][name];
				return this[cachedName][name] = orig.apply(this, arguments);
			};
		}

		/*
		PrivateFunction: reset(obj, names)

		Remove cached values for a set of memoized methods.

		Arguments:

			*obj* (object)

			The object containing cached values.

			*names* (array of strings)

			The names of methods whose values have been cached.
		*/

		function reset (obj, names) {
			for (var i = 0, l = names.length; i < l; i++) {
				delete obj['cached_' + names[i]];
			}
		}

		/*
		PrivateFunction: resetNamed(obj, meth, names)

		Remove cached values for a set of named properties for a method.

		Arguments:

			*obj* (object)

			The object containing cached values.

			*meth* (string)

			The name of the method whose values have been cached.

			*names* (array of strings)

			The names of the cached properties.

		function resetNamed (obj, meth, names) {
			var cache = obj['cached_' + meth];
			if (! cache) return;
			for (var i = 0, l = names.length; i < l; i++) {
				delete cache[names[i]];
			}
		}
		*/

		/*
		PrivateClass: Box

		Calculates and caches information about an element in the box model.

		Constructor:

			 (code)
			 new Box(el)
			 (end)

		Arguments:

			*el* (glow.dom.NodeList)

			The element that calculations will be performed on.
		*/

		var Box = function (el) {
			this.el = el;
		};

		Box.prototype = {

			/*
			Method: val(style)

			Get an pixel value for a CSS style.

			Arguments:

				*style* (string)

				The name of a CSS style (e.g. "margin-top".

			Returns:
				An integer number of pixels.
			*/

			val: function (style) {
				var val = parseInt(this.el.css(style));
				// TODO - fix dom so margin-left return value is always defined?
//				if (isNaN(val)) throw 'got NaN in val for ' + style + ': ' + this.el.css(style);
				return isNaN(val) ? 0 : val;
//				return val;
			},

			/*
			Method: width()

			Get the width of the element.

			Returns:
				An integer number of pixels.
			*/

			width: function () {
				return this.borderWidth()
					 - this.val('border-left-width')
					 - this.val('padding-left')
					 - this.val('padding-right')
					 - this.val('border-right-width');
			},

			/*
			Method: height()

			Get the height of the element.

			Returns:
				An integer number of pixels.
			*/

			height: function () {
				return this.borderHeight()
					 - this.val('border-top-width')
					 - this.val('padding-top')
					 - this.val('padding-bottom')
					 - this.val('border-bottom-width');
			},

			/*
			Method: offsetParentPageTop()

			Get the number of pixels from the top of nearest element with absolute, relative or fixed position to the
			top of the page.

			Returns:
				An integer number of pixels.
			*/
			offsetParentPageTop: function () {
				var el = this.el[0], pos, top;
				while (el = el.offsetParent) {
					pos = $(el).css('position');
					if (pos == 'absolute' || pos == 'fixed' || pos == 'relative') break;
				}
				if (! el) return 0;
  				top = el.offsetTop;
				while (el = el.offsetParent) {
					top += el.offsetTop;
				}
				return top;
			},

			/*
			Method: offsetTop()

			Get the number of pixels between the top of the nearest containing element with absolute or fixed
			position and the element. In most browsers this is the same as the offsetTop property, but it does
			the right thing in Internet Explorer as well.

			Returns:
				An integer number of pixels.
			*/
			// TODO - should the dom module have this function?
			// TODO - this seems to miss border width of intermediary elements in ie, take account of this
			offsetTop: function () {
				var res = 0, el = this.el[0], pos, top;
				if (glow.env.ie) {
					do {
						top = el.offsetTop;
						if (! isNaN(top)) res += top;
						el = el.offsetParent;
						if (el) pos = $(el).css('position');
					} while (el && ! (pos == 'absolute' || pos == 'fixed' || pos == 'relative'));
				}
				else {
					res = el.offsetTop;
				}
				if (glow.env.opera) {
					var parentBorder = parseInt($(el.offsetParent).css('border-top-width'));
					res -= isNaN(parentBorder) ? 0 : parentBorder;
				}
				//see https://bugzilla.mozilla.org/show_bug.cgi?id=307502
				if (glow.env.gecko) {
					var doc = document,
						compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(el.offsetParent, null) || doc.defaultView.getComputedStyle),
						parentBorderVal;
					if (compStyle.position == 'relative' && compStyle.overflow == 'hidden' && (parentBorderVal = parseInt(compStyle.borderTopWidth))) {
						res += parentBorderVal;
					}
				}
				return res;
			},

			/*
			Method: offsetLeft()

			Get the number of pixels between the left of the nearest containing element with absolute or fixed
			position and the element. In most browsers this is the same as the offsetLeft property, but it does
			the right thing in Internet Explorer as well.

			Returns:
				An integer number of pixels.
			*/
			// TODO - should the dom module have this function?
			offsetLeft: function () {
				
				var res = 0, el = this.el[0], pos, left;
				if (glow.env.ie) {
					do {
						left = el.offsetLeft;
						if (! isNaN(left)) res += left;
						el = el.offsetParent;
						if (el) pos = $(el).css('position');
					} while (el && ! (pos == 'absolute' || pos == 'fixed' || pos == 'relative'));
				}
				else {
					res = el.offsetLeft;
				}
				if (glow.env.opera) {
					var parentBorder = parseInt($(el.offsetParent).css('border-left-width'));
					res -= isNaN(parentBorder) ? 0 : parentBorder;
				}
				//see https://bugzilla.mozilla.org/show_bug.cgi?id=307502
				if (glow.env.gecko) {
					var doc = document,
						compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(el.offsetParent, null) || doc.defaultView.getComputedStyle),
						parentBorderVal;
					if (compStyle.position == 'relative' && compStyle.overflow == 'hidden' && (parentBorderVal = parseInt(compStyle.borderLeftWidth))) {
						res += parentBorderVal;
					}
				}
				return res;
			},

			/*
			Method: borderWidth()

			Get the width of the element from the left edge of the left border to the right
			edge of the right border.

			Returns:
				An integer number of pixels.
			*/

			borderWidth: function () {
				var width = this.el[0].offsetWidth;
				if (glow.env.khtml) {
					width -= this.val('margin-left')
							+ this.val('margin-right')	
							+ this.val('border-left-width')
							+ this.val('border-right-width');
				}
				return width;
			},

			/*
			Method: borderHeight()

			Get the height of an element from the top edge of the top border to the bottom
			edge of the bottom border.

			Returns:
				An integer number of pixels.
			*/

			borderHeight: function () {
				if (this._logicalBottom) {
					return this._logicalBottom - this.offsetTop();
				}
				var height = this.el[0].offsetHeight;
				if (glow.env.khtml) {
					height -= this.val('margin-top')
							+ this.val('margin-bottom')	
							+ this.val('border-top-width')
							+ this.val('border-bottom-width');
				}
				return height;
			},

			/*

			/*
			Method: outerWidth

			Get the width of the element in including margin, borders and padding.

			Returns:
				An integer number of pixels.
			*/

			outerWidth: function () {
				return this.borderWidth() + this.val('margin-left') + this.val('margin-right');
			},

			/*
			Method: outerHeight

			Get the height of the element in including margin, borders and padding. This
			does not take account of collapsable margins (i.e. it assumes the margins are
			present).

			Returns:
				An integer number of pixels.
			*/

			outerHeight: function () {
				return this.borderHeight() + this.val('margin-top') + this.val('margin-bottom');
			},

			/*
			Method: innerLeftPos

			Get the offset of the left edge of the content of the box (i.e. excluding
			margin, border and padding).

			Returns:
				An integer number of pixels.
			*/

			innerLeftPos: function () {
				return this.offsetLeft()
					 + this.val('border-left-width')
					 + this.val('padding-left');
			},

			/*
			Method: innerTopPos

			Get the offset of the top edge of the content of the box (i.e. excluding
			margin, border and padding).

			Returns:
				An integer number of pixels.
			*/

			innerTopPos: function () {
				return this.offsetTop()
					 + this.val('border-top-width')
					 + this.val('padding-top');
			},

			/*
			Method: surroundWidth

			Get the combined width of the horizontal margins, borders and paddings.

			Returns:
				An integer number of pixels.
			*/

			surroundWidth: function () {
				return this.val('margin-left')
					 + this.val('border-left-width')
					 + this.val('padding-left')
					 + this.val('padding-right')
					 + this.val('border-right-width')
					 + this.val('margin-right');
			},

			/*
			Method: surroundHeight

			Get the combined height of the horizontal margins, borders and paddings.

			Returns:
				An integer number of pixels.
			*/

			surroundHeight: function () {
				return this.val('margin-top')
					 + this.val('border-top-width')
					 + this.val('padding-top')
					 + this.val('padding-bottom')
					 + this.val('border-bottom-width')
					 + this.val('margin-bottom');
			},

			/*
			Method: verticalCenter

			Get the vertical offset of the center of the element from it's offset parent.

			Returns:
				An integer number of pixels.
			*/

			verticalCenter: function () {
				return this.offsetTop() + (this.outerHeight() / 2);
			},

			/*
			Method: verticalCenter

			Get the vertical offset of the center of the element from it's offset parent.

			Returns:
				An integer number of pixels.
			*/

			horizontalCenter: function () {
				return this.offsetTop() + (this.outerWidth() / 2);
			}

   		};

		for (var i in Box.prototype) {
			if (i == 'val') memoizeNamed(Box, i);
			else memoize(Box, i);
		}

		glow.lang.apply(Box.prototype, {

			/*
			Method: resetPosition

			Reset cached position values for the element.
			*/

			resetPosition: function () {
				reset(this, [
					'offsetTop',
					'offsetLeft',
					'borderTopPos',
					'borderLeftPos',
					'innerTopPos',
					'innerLeftPos',
					'verticalCenter',
					'horizontalCenter'
				]);
			},

			/*
			Method: setLogicalBottom(bottom)

			Set the logical value for the position of the bottom of the border (offsetTop + offsetHeight).

			Arguments:

				*bottom* (integer)

				The value to use for the bottom of the box.
			*/
			setLogicalBottom: function (bottom) {
				this._logicalBottom = bottom;
			},

			/*
			Method: boundsFor

			Get the the bounds for the left and top css properties of a child box to
			ensure that it stays within this element.

			Arguments:

				*childBox* (Box)

				A Box object representing the space taken up by the child element.

			Returns:
				An array of top, right, bottom and left pixel bounds for the top and left
				css properties of the child element.
			*/

			boundsFor: function (childBox) {
				var top, left, pos = this.el.css('position');
				if (pos == 'relative' || pos == 'absolute' || pos == 'fixed') {
				    top = left = 0;
				}
				else {
					top = this.innerTopPos();
					left = this.innerLeftPos();
				}
				return [
					top,										   // top
					left + this.width() - childBox.outerWidth(),   // right
					top  + this.height() - childBox.outerHeight(), // bottom
					left										   // left
				];
			},

			/*
			Method: outerBounds

			Get the top, right, bottom and left offsets of the outside edge of the border
			of the box.

			Returns:
				An array of integer pixel offsets for the top, right, bottom, left edges of the
				boxes border.
			*/

			outerBounds: function () {
				var left = this.offsetLeft(),
					top = this.offsetTop();
				return [
					top,
					left + this.borderWidth(),
					top + this.borderHeight(),
					left
				];
			},

			/*
			Method: intersectSize

			Get the intersection of this box with another box.

			Arguments:

				*that* (Box)

				A Box object to test for intersection with this box.

				*touches* (boolean)

				If true, then the boxes don't have to intersect but can merely touch.

			Returns:
				An integer number of square pixels that the the outside of the
				edge of the border of this box intersects with that of the passed
				in box.
			*/

			intersectSize: function (that, touches) {
				var a = this.outerBounds(), b = that.outerBounds();
				if (touches) {
					a[1]++; b[1]++; a[2]++; b[2]++;
				}
				return (
					a[2] < b[0] ? 0 :
					b[2] < a[0] ? 0 :
					a[0] < b[0] ? (a[2] < b[2] ? a[2] - b[0] : b[2] - b[0]) :
					b[2] < a[2] ? b[2] - a[0] : a[2] - a[0]
				) * (
					a[1] < b[3] ? 0 :
					b[1] < a[3] ? 0 :
					a[3] < b[3] ? (a[1] < b[1] ? a[1] - b[3] : b[1] - b[3]) :
					b[1] < a[1] ? b[1] - a[3] : a[1] - a[3]
				);
			},

			/*
			Method: sizePlaceholder

			Size and position a placeholder/drop indicator element to match that
			of the element.

			Arguments:

				*placeholder* (glow.dom.NodeList)

				The element that will be sized.

				*pos* (optional string)

				The value for the placeholder's CSS position. Defaults to the position
				of this element.

				*startLeft* (integer)

				The original left position of the element.

				*startTop* (integer)

				The original top position of the element.
			*/

			sizePlaceholder: function (placeholder, pos, startLeft, startTop) {
				var placeholderBox = new Box(placeholder),
					el = this.el,
					position = pos || el.css('position');
				placeholder.css('display', 'none');
				
				el.after(placeholder);
				
				placeholder.css('width', (this.outerWidth() - placeholderBox.surroundWidth()) + 'px');
				placeholder.css('height', (this.outerHeight() - placeholderBox.surroundHeight()) + 'px');
				
				placeholder.remove();
				
				placeholder.css('display', 'block');
				
				if (position != 'static') {
					placeholder.css('left', startLeft + 'px');
					placeholder.css('top', startTop + 'px');
				}
				placeholder.css('position', position);
			},

			/*
			Method: contains

			Check if a box is contained within this box.

			Arguments:

				*box* (Box)

				The box to test.

			Returns:
				Boolean, true if contained.
			*/

			contains: function (box) {
				var bounds = this.boundsFor(box), top = box.offsetTop(), left = box.offsetLeft();
				return top  >= bounds[0]  // top
					&& left <= bounds[1]  // right
					&& top  <= bounds[2]  // bottom
					&& left >= bounds[3]; // left
			},

			/*
			Method: containsPoint

			Arguments:

				*offset* (object)

				The offset to check - an object containing x and y integer pixel values.

			Returns:
				Boolean, true if the point over the visible part of the element (i.e. including the borders).
			*/

			containsPoint: function (offset) {
				var elementOffset = this.el.offset();
				return offset.x >= elementOffset.x
					&& offset.y >= elementOffset.y
					&& offset.x <= elementOffset.x + this.borderWidth()
					&& offset.y <= elementOffset.y  + this.borderHeight();
			},

			/*
			Method: positionedAncestorBox

			Get a new Box for nearest ancestor of the element that has position 'absolute', 'fixed' or 'relative'.

			Returns:
				An integer pixel offset.
			*/

			positionedAncestorBox: function () {
				var el = this.el.parent(), pos;
				while (el[0]) {
					pos = el.css('position') || 'static';
					if (pos == 'relative' || pos == 'absolute' || pos == 'fixed')
						return new Box(el);
					el = el.parent();
				}
				return null;
			}
		});



		function placeholderElement (el) {
			var tag = el[0].tagName.toLowerCase() == 'li' ? 'li' : 'div';
			var placeholder = create('<' + tag + '></' + tag + '>');
			if (tag == 'li') placeholder.css('list-style-type', 'none');
			return placeholder;
		}

		/*
		Class: glow.dragdrop.Draggable
		
		Constructor:
		
			(code)
			new glow.dragdrop.Draggable( element, [options] );
			(end)
		
		Arguments:
			
			*element* (selector/DOM Element/glow.dom.NodeList)
			
			The element that should be draggable. If a nodelist containing multiple element is passed,
			then only the first is made draggable.

			*options* (object)
			
			(optional) A hash containing options (see below).
			
			Options:
						
				*placeholder* (string)
			
				Defines the object that will left in place of the draggable while it is being dragged.

				_Possible values_: 

					'spacer' - an empty div is created where the draggable started.
			
					'clone' - an exact clone of the original element.

					'none' - no placeholder will be created.

				Default - 'spacer'.

				*placeholderClass* (string)

				The class that will be applied to the placeholder element. This can be used to indicate where
				the element has been dragged from, add opacity, etc.
			
				
				*handle* (selector/DOM Element/glow.dom.NodeList)
			
				Restrict dragging to an element within the draggable.
			
				*container* (selector/DOM Element/glow.dom.NodeList)
			
				Constrains dragging to within the bounds of the specified element.
			
				Default - By default the draggable is not restricted to any container.
			
				*dropTargets* (array)
			
				An array of <glow.dragdrop.DropTarget> object. Specifies which drop targets this draggable is associated with.

				*axis* (string)
			
				Restricts dragging to an axis.
			
				_Possible values_:
			
					'x' - Restricts dragging to the x-axis
			
					'y' - Restricts dragging to the y-axis
				
				Default - By default the draggable is not restricted to any axis.
			
				*dragPrevention* (array)
			
				An array of element names (strings). e.g. ['a', 'img']. Stops drag from starting when these elements are clicked. 
			
				Default - ['input', 'textarea', 'button', 'select', 'option', 'a']

				*onDrag* (function)
			
				Create an event listener that fires when the draggable starts being dragged.
			
				*onEnter* (function)
			
				Create an event listener that fires when the draggable is dragged over a drop target.
			
				*onLeave* (function)
			
				Create an event listener that fires when the draggable is dragged out of a drop target.
			
				*onDrop* (function)
			
				Create an event listener that fires when the draggable is dropped. The default action is
				to animate the draggable back to it's start position (can be cancelled by returning false
				or calling <glow.events.Event.preventDefault> on the passed in <glow.events.Event> object.

		Example:

			(code)
			// empty NodeList
			var myDraggable = new glow.dragdrop.Draggable('#draggable', {
				dropTargets : [ aDropZone ],
				container : '#container',
				onDrag : function () {
					this.element.css('opacity', '0.7');
				},
				onDrop : function () {
					this.element.css('opacity', '1');
				}
			});
			(end)

		*/
		r.Draggable = function (el, opts) {
			
			/*
			Group: Properties

			Property: element (glow.dom.NodeList)

			The draggable element.

			*/

			this.element = $(el);
			this._opts = opts = glow.lang.apply({
				dragPrevention   : ['input', 'textarea', 'button', 'select', 'option', 'a'],
				placeholder	     : 'spacer',
				placeholderClass : 'glow-dragdrop-placeholder'
			}, opts || {});

			this._preventDrag = [];
			for (var i = 0, l = opts.dragPrevention.length; i < l; i++) {
				this._preventDrag[i] = opts.dragPrevention[i].toLowerCase();
			}

			if (opts.container) { this.container = $(opts.container); }
			this._handle = opts.handle && this.element.get(opts.handle) || this.element;

			if (opts.dropTargets) this.dropTargets = $(opts.dropTargets);

				//used for IE literal edge case bug fix
				//this._mouseUp = true;
				//bug fix to get document.body.scrollTop to return true value (not 0) if using transitional 4.01 doctype
				//get('body')[0].style.overflow = 'auto';
				//this._opts = o, this._targetCoords = [], this.isOverTarget = false;

			var listeners = this._listeners = [],
				i = 0;

			if (opts.onDrag)  listeners[i++] = addListener(this, 'drag',  this._opts.onDrag,  this);
			if (opts.onEnter) listeners[i++] = addListener(this, 'enter', this._opts.onEnter, this);
			if (opts.onLeave) listeners[i++] = addListener(this, 'leave', this._opts.onLeave, this);
			if (opts.onDrop)  listeners[i++] = addListener(this, 'drop',  this._opts.onDrop,  this);

			this._dragListener = addListener(this._handle, 'mousedown', this._startDragMouse, this);

			return;
		};


		/*
		Group: Methods
		*/

		//var applyFloatBugfix = glow.env.ie;

		r.Draggable.prototype = {

			/*
			PrivateMethod: _createPlaceholder

			Create an element that occupies the space where the draggable has been dragged from.
			*/

			_createPlaceholder: function () {
				var el = this.element,
					placeholder,
					box = this._box;
				
				
				if (this._opts.placeholder == 'clone') {
					placeholder = el.clone();
				}
				else { // placeholder == 'spacer'
					placeholder = placeholderElement(el);
				}
				if (this._opts.placeholderClass)
					placeholder.addClass(this._opts.placeholderClass);
				box.sizePlaceholder(placeholder, null, this._startLeft, this._startTop);
				el.after(placeholder);
				this._placeholder = placeholder;
			},

			/*
			PrivateMethod: _removePlaceholder

			Removes the placeholder (see above) from the document.
			*/

			_removePlaceholder: function () {
				this._placeholder.remove();
			},

			/*
			PrivateMethod: _resetPosition

			Sets the position CSS property to what it started as without moving the draggable. If the
			original position was 'static' and making it 'static' again would mean moving the draggable,
			then the position is set to 'relative'.
			*/

			_resetPosition: function () {
				var origPos = this._preDragPosition,
					el = this.element,
					box = this._box,
					startOffset = this._startOffset,
					pos = el.css('position'),
					newLeft,
					newTop;
				box.resetPosition();
				var	offset = {
					x: box.offsetLeft() - box.val('margin-left'),
					y: box.offsetTop() - box.val('margin-top')
				};

				if (this._placeholder || this._dropIndicator) {
					el.remove();
				}
				if (origPos == 'static' && offset.y == startOffset.y && offset.x == startOffset.x) {
					el.css('position', 'static');
					el.css('left', '');
					el.css('top', '');
				}
				else {
					el.css('z-index', this._preDragZIndex);
					el.css('position', origPos == 'static' ? 'relative' : origPos);
					if (origPos == 'static') {
						newLeft = offset.x - startOffset.x;
						newTop = offset.y - startOffset.y;
					}
					else if (origPos == 'relative' && pos != 'relative') {
						newLeft = this._startLeft + (offset.x - startOffset.x);
						newTop = this._startTop + (offset.y - startOffset.y);
					}
					if (pos != origPos) {
						el.css('left', newLeft ? newLeft + 'px' : '');
						el.css('top', newTop ? newTop + 'px' : '');
					}
				}
				if (this._dropIndicator) {
					var parent = this._dropIndicator.parent()[0];
					if (parent)	parent.replaceChild(el[0], this._dropIndicator[0]);
					delete this._dropIndicator;
					if (this._placeholder) {
						this._placeholder.remove();
						delete this._placeholder;
					}
				}
				else if (this._placeholder) {
					var parent = this._placeholder.parent()[0];
					if (parent)	parent.replaceChild(el[0], this._placeholder[0]);
					delete this._placeholder;
				}
			},

			/*
			PrivateFunction: _startDragMouse
			
			Start the draggable dragging when the mousedown event is fired.

			Arguments:

				*e* (glow.events.Event)

				The mousedown event that caused the listener to be fired.
			*/

			_startDragMouse: function (e) {
				var preventDrag = this._preventDrag,
					source = e.source,
					tag = source.tagName.toLowerCase();

				for (var i = 0, l = preventDrag.length; i < l; i++) {
					if (preventDrag[i] == tag) {
						return;
					}
				}

				if (this._dragging == 1)
					return this.endDrag();
				else if (this._dragging)
					return;

				// _dragging set to 1 during drag, 2 while ending drag and back to 0 when ready for new drag
				this._dragging = 1;

				var el = this.element,
					container = this.container,
					opts = this._opts,
					box = this._box = new Box(el);

				this._preDragPosition = el.css('position');

				if (container) {
					this._containerBox = new Box(container);
					this._bounds = this._containerBox.boundsFor(box);
				}
				else {
					delete this._bounds;
				}

				this._mouseStart = {
					x: e.pageX,
					y: e.pageY
				};
	
				var startOffset = this._startOffset = {
					x: box.offsetLeft(),
					y: box.offsetTop()
				};

				this._preDragStyle = el.attr('style');

				this._preDragZIndex = el.css('z-index');

				el.css('z-index', _zIndex++);

				this._startLeft = el[0].style.left ? parseInt(el[0].style.left) : 0;
				this._startTop = el[0].style.top ? parseInt(el[0].style.top) : 0;


				if (opts.placeholder && opts.placeholder != 'none')
					this._createPlaceholder();

				el.css('position', 'absolute');
				el.css('left', startOffset.x + 'px');
				el.css('top', startOffset.y + 'px');

				if(_ieStrict) {
					this._scrollY = document.documentElement.scrollTop;
					this._innerHeight = document.documentElement.clientHeight;
				}
				else if(_ieTrans){
					this._scrollY = document.body.scrollTop;
					this._innerHeight = document.body.clientHeight;
				}
				else {
					this._scrollY = window.scrollY;
					this._innerHeight = window.innerHeight;
				}

				var documentHeight = $(document).height();
				this._bodyHeight  = documentHeight < this._innerHeight ? this._innerHeight : documentHeight;

				//fire the drag event
				fire(this, 'drag');

				var cancelFunc = function () { return false },
					doc = document.documentElement;
				
				if (this.dropTargets) {
					var event = new events.Event();
					event.draggable = this;
					for (var i = 0, l = this.dropTargets.length; i < l; i++) {
						fire(this.dropTargets[i], 'active', event);
					}
					
					this._mousePos = {
						x: e.pageX,
						y: e.pageY
					};
					this._testForDropTargets();
				}

				this._dragListeners = [				
					addListener(doc, 'selectstart', cancelFunc),
					addListener(doc, 'dragstart',   cancelFunc),
					addListener(doc, 'mousedown',   cancelFunc),
					addListener(doc, 'mousemove',   this._dragMouse, this),
					addListener(doc, 'mouseup',	 this._releaseElement, this)
				];
				return false;
			},
			
			/*
			PrivateFunction: _dragMouse
			
			Move the draggable when a mousemove event is received.
				
			Arguments:
			
				*e* (glow.events.Event)

				The mousedown event that caused the listener to be fired.
			*/

			_dragMouse: function (e) {
				var element = this.element,
					newX = this._opts.axis == 'y' ?
						this._startOffset.x:
						(this._startOffset.x + e.pageX - this._mouseStart.x),
					newY = this._opts.axis == 'x' ?
						this._startOffset.y:
						(this._startOffset.y + e.pageY - this._mouseStart.y),
					bounds = this._bounds;

				// only pay for the function call if we have a container or an axis (consider coding in axis here)
				if (bounds) {
					newX = newX < bounds[3] ? bounds[3] : newX > bounds[1] ? bounds[1] : newX;
					newY = newY < bounds[0] ? bounds[0] : newY > bounds[2] ? bounds[2] : newY;
				}

				element[0].style.left = newX + 'px';
				element[0].style.top = newY + 'px';

				//if there are dragTargets check if the draggable is over the target
				if (this.dropTargets) {
					this._mousePos = { x: e.pageX, y: e.pageY };
				}
				// check for IE mouseup outside of page boundary
				if(_ie && e.nativeEvent.button == 0) {
					this._releaseElement(e);
					return false;
				};
				return false;
			},

			/*
			PrivateFunction: _testForDropTarget

			Check if the draggable is over a drop target. Sets the activeTarget property of the draggable
			to the drop target that the draggable is over, if any.
				
			Arguments:

				*mousePos* (object)

				The position of the mouse pointer relative to the document. The object has x and y integer
				pixel properties.
			*/
			_testForDropTargets: function (fromTimeout) {

				if (! this._lock) this._lock = 0;
				if (fromTimeout) this._lock--;
				else if (this.lock) return;

				if (this._dragging != 1) return;

				var previousTarget = this.activeTarget,
					activeTarget,
					targets = this.dropTargets,
					target,
					targetBox,
					box = this._box,
					mousePos = this._mousePos;

				box.resetPosition();

				var maxIntersectSize = 0;
				for (var i = 0, l = targets.length; i < l; i++) {
					target = targets[i];
					targetBox = target._box;
					if (target._opts.tolerance == 'contained') {
						if (targetBox.contains(box)) {
							activeTarget = target;
							break;
						}
					}
					else if (target._opts.tolerance == 'cursor') {
						if (targetBox.containsPoint(mousePos)) {
							activeTarget = target;
							break;
						}
					}
					else {
						var intersectSize = targetBox.intersectSize(box, true);
						if (intersectSize > maxIntersectSize) {
							maxIntersectSize = intersectSize;
							activeTarget = target;
						}
					}
				}
				this.activeTarget = activeTarget;
				
				// enter events
				if (activeTarget !== previousTarget) {
					if (activeTarget) {
						// enter on the target
						var draggableEnterEvent = new events.Event();
						draggableEnterEvent.draggable = this;
						fire(activeTarget, 'enter', draggableEnterEvent);

						// enter on this (the draggable)
						var enterTargetEvent = new events.Event();
						enterTargetEvent.dropTarget = activeTarget;
						fire(this, 'enter', enterTargetEvent);
					}

					if (previousTarget) {
						// leave on target
						var draggableLeaveEvent = new events.Event();
						draggableLeaveEvent.draggable = this;
						fire(previousTarget, 'leave', draggableLeaveEvent);
						
						// leave on this (draggable)
						var leaveTargetEvent = new events.Event();
						leaveTargetEvent.dropTarget = previousTarget;
						fire(this, 'leave', leaveTargetEvent);
					}
				}
				// place the drop indicator in the drop target (not in the drop target class for speed)
				if (activeTarget && activeTarget._opts.dropIndicator != 'none') {
					var childBox,
						childBoxes = activeTarget._childBoxes,
						children = activeTarget._children;
					box.resetPosition();
					var totalHeight = activeTarget._box.innerTopPos();
					var draggablePosition = mousePos.y - box.offsetParentPageTop();
					var placed = 0;
					for (var i = 0, l = childBoxes.length; i < l; i++) {
						if (children[i] == this.element[0]) continue;
						childBox = childBoxes[i];
						totalHeight += childBox.outerHeight();
						if (draggablePosition <= totalHeight) {
							if (activeTarget._dropIndicatorAt != i) {
								$(childBox.el).before(activeTarget._dropIndicator);
								activeTarget._dropIndicatorAt = i;
							}
							placed = 1;
							break;
						}
					}
					if (! placed) {
						if (childBox) {
							$(childBox.el).after(activeTarget._dropIndicator);
							activeTarget._dropIndicatorAt = i + 1;
						}
						else {
							activeTarget.element.append(activeTarget._dropIndicator);
							activeTarget._dropIndicatorAt = 0;
						}
					}
				}

				this._lock++;
				var this_ = this;
				setTimeout(function () { this_._testForDropTargets(1) }, 100);
			},

			/*
			PrivateMethod: releaseElement

			Finish the drag when a mouseup event is recieved.
				
			Arguments:

				*e* (glow.events.Event)

				The mouseup event that caused the listener to be fired.
			*/

			_releaseElement: function () {
				if (this._dragging != 1) return;
				this._dragging = 2;

				var i, l;

				//call the onInactive function on all the dropTargets for this draggable
				var dropTargets = this.dropTargets,
					activeTarget = this.activeTarget;

				if (dropTargets) {
					for (i = 0, l = dropTargets.length; i < l; i++) {
						var event = new events.Event();
						event.draggable = this;
						event.droppedOnThis = activeTarget && activeTarget == dropTargets[i];
						fire(dropTargets[i], 'inactive', event);
					}
				}

				if (activeTarget) {
					var event = new events.Event();
					event.draggable = this;
					fire(activeTarget, 'drop', event);
				}
		
				var dragListeners = this._dragListeners;
				for (i = 0, l = dragListeners.length; i < l; i++) {
					events.removeListener(dragListeners[i]);
				}
				
				var dropEvent = fire(this, "drop");
				if (! dropEvent.defaultPrevented() && this.dropTargets) {
					this.returnHome();
				}
				else {
					this.endDrag();
				}

			},

			/*
			Method: endDrag

			Finishes dragging the draggable. Removes the placeholder (if any) and resets the position CSS property
			of the draggable.

			TODO - revist this code example
				
			N.B. This is called by default but if you overwrite the onDrop function then you will have to call it youoriginal
			(code)
			// empty NodeList
			var myDraggable = new glow.dragdrop.Draggable('#draggable', {
				onDrop = function(e){
					do some stuff that takes a while.....
					this.endDrag();
					false;
				}
			});
			(end)
			*/

			endDrag: function(){
				if (this._dragging != 2) return;
				this._dragging = 0;

				//remove any helpers/placeholders
				if (this._reset) {
					this._reset();
					delete this._reset;
				}
			
				if (this.placeholder) {
					this.placeholder.remove();
				}
				this._resetPosition();
				delete this.activeTarget;
				fire(this, "afterDrop");
			},

			/*
			Event: returnHome
			
			Animates the Draggable back to it's start position and calls endDrag() at the end of the
			transition. This is called by default when the Draggable, that has a DragTarget, is dropped.
			However if you override the default onDrop function you may want to call this function your
			original
				
			Arguments
				tween (function)
				The animation you wish to used for easing the Draggable. See <glow.tweens>. This is optional, the default is a linear tween. e.g. glow.tweens.easeboth
			*/

			returnHome: function(tween){
				var mytween = (tween) ? tween : glow.tweens.linear(),
					el = this.element,
					distance = Math.pow(
						Math.pow(this._startOffset.x - this._box.offsetLeft(), 2)
						+ Math.pow(this._startOffset.y - this._box.offsetTop(), 2),
						0.5
					),
					duration = 0.3 + (distance / 1000);

				var channels = [[
					glow.anim.css(el, duration, {
							left: { from: this._box.offsetLeft(), to : this._startOffset.x },
							top : { from: this._box.offsetTop(), to : this._startOffset.y }
					}, { tween: mytween })
				]];

				if (this._dropIndicator) {
					channels.push([glow.anim.css(this._dropIndicator, duration - 0.1, { opacity: { to: 0 } })]);
				}

				var timeline = new glow.anim.Timeline(channels);
				addListener(timeline, 'complete', function () {
					this.endDrag();
				}, this);
				timeline.start();
				return;
			}
		};
		
		
		var dropTargetId = 0;

		/*
		Class: glow.dragdrop.DropTarget
		
		Constructor:

			(code)
			new glow.dragdrop.DropTarget( element, [options] );
			(end)

		Arguments:
			
			*element* (selector/DOM Element/glow.dom.NodeList)
			
			The element that you want to allow dropping onto, if you pass a glow.dom.NodeList
			containing multiple elements then only the first will be made into a drop target.

			*options* (object)
			
			(optional) A hash of options (see below).
			
			Options:
			
				*tolerance* (string)
			
				The point at which, while the draggable moves over the target, the target becomes active. 
			
				_Possible values_:
				
					'intersect' - The target becomes active as soon as any part of the draggable is over the target.

					'cursor' - The target becomes active when the cursor is over the target. 

					'contained' - The target only becomes active once the whole draggable is within the target.

				Default - The default is 'intersect'
			 
				*dropIndicator* (string)

				Create an element to indicate where the draggable will end up if dropped over the drop target.

				_Possible values_:

					'spacer' - an empty div will be added to the drop target to indicate where the draggable will be dropped.

					'none' - no drop indicator will be created.

				Default - The deafult is 'none'.

				*dropIndicatorClass* (string)

				The class that will be applied to the drop indicator element. This is used to style the drop indicator.

				Default - 'glow-dragdrop-dropindicator'.

				*onEnter* (function)
			
				Create an event listener that is fired when a draggable is dragged over the drop target.
			
				*onLeave* (function)
			
				Create an event listener that is fired when a draggable is dragged out of the drop target.
			
				*onDrop* (function)
			
				Create an event listener that is fired when a draggable is dropped on the drop target.
			
				*onActive* (function)
			
				Create an event listener that is fired when an associated draggable starts being dragged.
			
				*onInactive* (function)
			
				Create an event listener that is fired when an associated draggable stops being dragged.
			 
		Example:
			
			(code)
			var aDropZone = new glow.dragdrop.DropTarget('#dropZone', {
				onActive: function(e){
						this.original.css('border', '2px solid blue');
				},
				onInactive: function(e){
						this.original.css('border', '');
						this.original.css('opacity', '1');
				},
				onEnter: function(e){
						this.original.css('opacity', '0.2');
				},
				onLeave: function(e){
						this.original.css('opacity', '1');
				},
				onDrop: function(e){
						this.original.css('backgroundColor', 'green');
				}
			});
			(end)
		*/

		r.DropTarget = function(el, opts) {
			el = this.element = $(el);
			if (! el.length) throw 'no element passed into DropTarget constuctor';
			if (el.length > 1) throw 'more than one element passed into DropTarget constructor';

			// id is for indexing drop targets in an object for getting to them quickly
			this._id = ++dropTargetId;

			this._opts = opts = glow.lang.apply({
				dropIndicator	   : 'none',
				acceptDropOutside  : false,
				dropIndicatorClass : 'glow-dragdrop-dropindicator',
				tolerance          : 'intersect'
			}, opts || {});

			if (opts.onActive)   addListener(this, 'active',   opts.onActive);
			if (opts.onInactive) addListener(this, 'inactive', opts.onInactive);
			if (opts.onEnter)	 addListener(this, 'enter',	opts.onEnter);
			if (opts.onLeave)	 addListener(this, 'leave',	opts.onLeave);
			if (opts.onDrop)	 addListener(this, 'drop',	 opts.onDrop);

			this._activeListener = addListener(this, 'active', this._onActive);
			this._activeListener = addListener(this, 'inactive', this._onInactive);

			return this;
		};
		

		r.DropTarget.prototype = {

			/*
			Method: setLogicalBottom(height)

			Set a bottom pos to use for detecting if a draggable is over the drop target to use
			other than the actual bottom of the drop target (offsetTop + offsetHeight).

			Arguments:
			
				*bottom* (integer)

				The number of pixels to use for the bottom of the drop target.
			*/
			setLogicalBottom: function (bottom) {
				this._logicalBottom = bottom;
			},

			/*
			PrivateMethod: _onActive

			Respond to an associated draggable when it starts to be dragged.

			Arguments:

				*e* (glow.events.Event)

				The active event that caused the event listener to be fired.
			*/
			
			_onActive: function (e) {
				var draggable = e.draggable;

				this._box = new Box(this.element);
				if (this._logicalBottom) this._box.setLogicalBottom(this._logicalBottom);

				if (this._opts.dropIndicator == 'none') return;

				this._onEnterListener = addListener(this, 'enter', this._onEnter);
				this._onLeaveListener = addListener(this, 'leave', this._onLeave);

				this._dropIndicator = placeholderElement(draggable.element);

				if (this._opts.dropIndicatorClass) {
					this._dropIndicator.addClass(this._opts.dropIndicatorClass);
				}
				draggable._box.sizePlaceholder(this._dropIndicator, 'relative', 0, 0);
				
				
				var children = this._children = $(this.element.children()).filter(function () {
					var el = $(this);
					return (! e.draggable._placeholder || ! el.eq(e.draggable._placeholder))
						&& (! this._dropIndicator || ! el.eq(this._dropIndicator));
				});
				var childBoxes = this._childBoxes = [];
				children.each(function (i) {
					childBoxes[i] = new Box($(children[i]));
				});
			},

			/*
			PrivateMethod: _onInactive

			Respond to an associated draggable when it finishes being dragged.

			Arguments:

				*e* (glow.events.Event)

				The inactive event that caused the event listener to be fired.
			*/

			_onInactive: function (e) {
				removeListener(this._onEnterListener);
				removeListener(this._onLeaveListener);

				delete this._box;

				if (this._opts.dropIndicator == 'none') return;

				if (! e.droppedOnThis && this._dropIndicator) {
					this._dropIndicator.remove();
					delete this._dropIndicator;
				}
				delete this._childBoxes;
				delete this._children;
			},

			/*
			PrivateMethod: _onEnter

			Respond to an associated draggable being dragged over the drop target.

			Arguments:

				*e* (glow.events.Event)

				The enter event that caused the event listener to be fired.
			*/

			_onEnter: function () {
				this._dropIndicatorAt = -1;
			},

			/*
			PrivateMethod: _onLeave

			Respond to an associated draggable being dragged out of the drop target.

			Arguments:

				*e* (glow.events.Event)

				The leave event that caused the event listener to be fired.
			*/

			_onLeave: function () {
				this._dropIndicator.remove();
			},

			/*
			Method: moveToPosition

			Insert the draggable's element within the drop target where the drop indicator currently is. Sets
			the start offset of the drag to the position of the drop indicator so that it will be animated
			to it's final location, rather than where the drag started.
			*/

			moveToPosition : function (draggable) {
				var dropIndicator = this._dropIndicator,
					box = new Box(dropIndicator);
				//dropIndicator.after(draggable.element);
				var marginLeft = parseInt(dropIndicator.css('margin-left')),
					marginTop = parseInt(dropIndicator.css('margin-top'));
				if (isNaN(marginLeft)) marginLeft = 0;
				if (isNaN(marginTop)) marginTop = 0;
				draggable._startOffset = {
					x: box.offsetLeft() - marginLeft,
					y: box.offsetTop() - marginTop
				};
				draggable._dropIndicator = dropIndicator;
				delete this._dropIndicator;
			}

			/*
			Group: Events

				Event: active

					Fired when a draggable linked to this drop target starts being dragged.
				
				Event: inactive

					Fired when a draggable linked to this drop target stops dragging.
				
				Event: enter

					Fired when a draggable linked to this drop target is dragged over the target.
				
				Event: leave
					
					Fired when a draggable linked to this drop target is dragged out of the target.
				
				Event: drop
					
					Fired when a draggable linked is dropped on this drop target.
			*/

		}
		return r
		
		
	}
});
/*@end @*/