// Spry.Effect.js - version 0.38 - Spry Pre-Release 1.6.1
//
// Copyright (c) 2006. Adobe Systems Incorporated.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of Adobe Systems Incorporated nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

var Spry;

if (!Spry) Spry = {};

Spry.forwards = 1; // const
Spry.backwards = 2; // const

if (!Spry.Effect) Spry.Effect = {};

Spry.Effect.Transitions = {
	linearTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		return begin + (time / duration) * change;
	},
	sinusoidalTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		return begin + ((-Math.cos((time/duration)*Math.PI)/2) + 0.5) * change;
	},
	squareTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		return begin + Math.pow(time/duration, 2) * change;
	},
	squarerootTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		return begin + Math.sqrt(time/duration) * change;
	},
	fifthTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		return begin + Math.sqrt((-Math.cos((time/duration)*Math.PI)/2) + 0.5) * change;
	},
	circleTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		var pos = time/duration;
		return begin + Math.sqrt(1 - Math.pow((pos-1), 2))* change;
	},
	pulsateTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		return begin + (0.5 + Math.sin(17*time/duration)/2) * change;
	},
	growSpecificTransition: function(time, begin, change, duration)
	{
		if (time > duration) return change+begin;
		var pos = time/duration;
		return begin + (5 * Math.pow(pos, 3) - 6.4 * Math.pow(pos, 2) + 2 * pos) * change;
	}
};
for (var trans in Spry.Effect.Transitions)
{
	Spry[trans] = Spry.Effect.Transitions[trans];
}
//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Registry
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Registry = function()
{
	this.effects = [];
};

Spry.Effect.Registry.prototype.getRegisteredEffect = function(element, options) 
{
	var a = {};
	a.element = Spry.Effect.getElement(element);
	a.options = options;

	for (var i=0; i<this.effects.length; i++)
		if (this.effectsAreTheSame(this.effects[i], a))
			return this.effects[i].effect;

	return false;
};

Spry.Effect.Registry.prototype.addEffect = function(effect, element, options)
{
	if (!this.getRegisteredEffect(element, options))
	{
		var len = this.effects.length;
		this.effects[len] = {};
		var eff = this.effects[len];
		eff.effect = effect;
		eff.element = Spry.Effect.getElement(element);
		eff.options = options;
	}
};

Spry.Effect.Registry.prototype.effectsAreTheSame = function(effectA, effectB)
{
	if (effectA.element != effectB.element)
		return false;

	var compare = Spry.Effect.Utils.optionsAreIdentical(effectA.options, effectB.options);
	// reset finish and setup functions
	if (compare)
	{
		if (typeof effectB.options.setup == 'function')
			effectA.options.setup = effectB.options.setup;

		if (typeof effectB.options.finish == 'function')
			effectA.options.finish = effectB.options.finish;
	}		

	return compare;
};

var SpryRegistry = new Spry.Effect.Registry;

//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Utils
//
//////////////////////////////////////////////////////////////////////

if (!Spry.Effect.Utils) Spry.Effect.Utils = {};

Spry.Effect.Utils.showError = function(msg)
{
	alert('Spry.Effect ERR: ' + msg);
};
Spry.Effect.Utils.showInitError = function(effect){
	Spry.Effect.Utils.showError('The ' + effect + ' class can\'t be accessed as a static function anymore. '+ "\n" + 'Please read Spry Effects migration documentation.');
	return false;
};
Spry.Effect.Utils.Position = function()
{
	this.x = 0; // left
	this.y = 0; // top
	this.units = "px";
};

Spry.Effect.Utils.Rectangle = function()
{
	this.width = 0;
	this.height = 0;
	this.units = "px";
};

Spry.Effect.Utils.intToHex = function(integerNum) 
{
	var result = integerNum.toString(16);
	if (result.length == 1)
		result = "0" + result;
	return result;
};

Spry.Effect.Utils.hexToInt = function(hexStr)
{
	return parseInt(hexStr, 16);
};

Spry.Effect.Utils.rgb = function(redInt, greenInt, blueInt)
{
	var intToHex = Spry.Effect.Utils.intToHex;
	var redHex = intToHex(redInt);
	var greenHex = intToHex(greenInt);
	var blueHex = intToHex(blueInt);
	compositeColorHex = redHex.concat(greenHex, blueHex).toUpperCase();
	compositeColorHex = '#' + compositeColorHex;
	return compositeColorHex;
};

Spry.Effect.Utils.longColorVersion = function(color){
	if ( color.match(/^#[0-9a-f]{3}$/i) ){
		var tmp = color.split('');
		var color = '#';
		for (var i = 1; i < tmp.length; i++){
			color += tmp[i] + '' + tmp[i];	
		}
	}
	return color;
};

Spry.Effect.Utils.camelize = function(stringToCamelize)
{
	if (stringToCamelize.indexOf('-') == -1){
		return stringToCamelize;	
	}
	var oStringList = stringToCamelize.split('-');
	var isFirstEntry = true;
	var camelizedString = '';

	for(var i=0; i < oStringList.length; i++)
	{
		if(oStringList[i].length>0)
		{
			if(isFirstEntry)
			{
				camelizedString = oStringList[i];
				isFirstEntry = false;
			}
			else
			{
				var s = oStringList[i];
				camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
			}
		}
	}

	return camelizedString;
};

Spry.Effect.Utils.isPercentValue = function(value)
{
	var result = false;
	if (typeof value == 'string' && value.length > 0 && value.lastIndexOf("%") > 0)
		result = true;

	return result;
};

Spry.Effect.Utils.getPercentValue = function(value)
{
	var result = 0;
	try
	{
		result = Number(value.substring(0, value.lastIndexOf("%")));
	}
	catch (e) {Spry.Effect.Utils.showError('Spry.Effect.Utils.getPercentValue: ' + e);}
	return result;
};

Spry.Effect.Utils.getPixelValue = function(value)
{
	var result = 0;
	if (typeof value == 'number') return value;
	var unitIndex = value.lastIndexOf("px");
	if ( unitIndex == -1)
		unitIndex = value.length;
	try
	{
		result = parseInt(value.substring(0, unitIndex), 10);
	}
	catch (e){}
	return result;
};

Spry.Effect.Utils.getFirstChildElement = function(node)
{
	if (node)
	{
		var childCurr = node.firstChild;
		while (childCurr)
		{
			if (childCurr.nodeType == 1) // Node.ELEMENT_NODE
				return childCurr;

			childCurr = childCurr.nextSibling;
		}
	}

	return null;
};

Spry.Effect.Utils.fetchChildImages = function(startEltIn, targetImagesOut)
{
	if(!startEltIn  || startEltIn.nodeType != 1 || !targetImagesOut)
		return;

	if(startEltIn.hasChildNodes())
	{
		var childImages = startEltIn.getElementsByTagName('img');
		var imageCnt = childImages.length;
		for(var i=0; i<imageCnt; i++)
		{
			var imgCurr = childImages[i];
			var dimensionsCurr = Spry.Effect.getDimensions(imgCurr);
			targetImagesOut.push([imgCurr,dimensionsCurr.width,dimensionsCurr.height]);
		}
	}
};

Spry.Effect.Utils.optionsAreIdentical = function(optionsA, optionsB)
{
	if(optionsA == null && optionsB == null)
		return true;

	if(optionsA != null && optionsB != null)
	{
		var objectCountA = 0;
		var objectCountB = 0;

		for (var propA in optionsA) objectCountA++;
		for (var propB in optionsB) objectCountB++;

		if(objectCountA != objectCountB)
			return false;

		for (var prop in optionsA)
		{
			var typeA = typeof optionsA[prop];
			var typeB = typeof optionsB[prop];
			if ( typeA != typeB || (typeA != 'undefined' && optionsA[prop] != optionsB[prop]))
				return false;
		}

		return true;
	}

	return false;
};

Spry.Effect.Utils.DoEffect = function (effectName, element, options)
{
	if (!options)
		var options = {};

	options.name = effectName;
	var ef = SpryRegistry.getRegisteredEffect(element, options);
	if (!ef)
	{
		ef = new Spry.Effect[effectName](element, options);
		SpryRegistry.addEffect(ef, element, options);
	}
	ef.start();
	return true;
};
//////////////////////////////////////////////////////////////////////
//
//  The notification class
//
//////////////////////////////////////////////////////////////////////
if (!Spry.Utils) Spry.Utils = {};

Spry.Utils.Notifier = function()
{
	this.observers = [];
	this.suppressNotifications = 0;
};

Spry.Utils.Notifier.prototype.addObserver = function(observer)
{
	if (!observer)
		return;

	// Make sure the observer isn't already on the list.

	var len = this.observers.length;
	for (var i = 0; i < len; i++)
		if (this.observers[i] == observer) return;

	this.observers[len] = observer;
};

Spry.Utils.Notifier.prototype.removeObserver = function(observer)
{
	if (!observer)
		return;

	for (var i = 0; i < this.observers.length; i++)
	{
		if (this.observers[i] == observer)
		{
			this.observers.splice(i, 1);
			break;
		}
	}
};

Spry.Utils.Notifier.prototype.notifyObservers = function(methodName, data)
{
	if (!methodName)
		return;

	if (!this.suppressNotifications)
	{
		var len = this.observers.length;
		for (var i = 0; i < len; i++)
		{
			var obs = this.observers[i];
			if (obs)
			{
				if (typeof obs == "function")
					obs(methodName, this, data);
				else if (obs[methodName])
					obs[methodName](this, data);
			}
		}
	}
};

Spry.Utils.Notifier.prototype.enableNotifications = function()
{
	if (--this.suppressNotifications < 0)
	{
		this.suppressNotifications = 0;
		Spry.Effect.Utils.showError("Unbalanced enableNotifications() call!\n");
	}
};

Spry.Utils.Notifier.prototype.disableNotifications = function()
{
	++this.suppressNotifications;
};

//////////////////////////////////////////////////////////////////////
//
// DHTML manipulation
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.getElement = function(ele)
{
	var element = ele;
	if (typeof ele == "string")
		element = document.getElementById(ele);

	if (element == null) 
		Spry.Effect.Utils.showError('Element "' + ele + '" not found.');
	
	return element;
};

Spry.Effect.getStyleProp = function(element, prop)
{
	var value;
	var camelized = Spry.Effect.Utils.camelize(prop);
	try
	{
		if (element.style)
			value = element.style[camelized];

		if (!value)
		{
			if (document.defaultView && document.defaultView.getComputedStyle)
			{
				var css = document.defaultView.getComputedStyle(element, null);
				value = css ? css.getPropertyValue(prop) : null;
			}
			else if (element.currentStyle) 
			{
					value = element.currentStyle[camelized];
			}
		}
	}
	catch (e) {Spry.Effect.Utils.showError('Spry.Effect.getStyleProp: ' + e);}

	return value == 'auto' ? null : value;
};

Spry.Effect.setStyleProp = function(element, prop, value)
{
	try
	{
		element.style[Spry.Effect.Utils.camelize(prop)] = value;
	}
	catch (e) {Spry.Effect.Utils.showError('Spry.Effect.setStyleProp: ' + e);}
};

Spry.Effect.getStylePropRegardlessOfDisplayState = function(element, prop, displayElement)
{
	var refElement = displayElement ? displayElement : element;
	var displayOrig = Spry.Effect.getStyleProp(refElement, 'display');
	var visibilityOrig = Spry.Effect.getStyleProp(refElement, 'visibility');

	if(displayOrig == 'none')
	{
		Spry.Effect.setStyleProp(refElement, 'visibility', 'hidden');
		Spry.Effect.setStyleProp(refElement, 'display', 'block');

		if(window.opera) // opera needs focus to calculate the size for hidden elements
			refElement.focus();
	}

	var styleProp = Spry.Effect.getStyleProp(element, prop);

	if(displayOrig == 'none') // reset the original values
	{
		Spry.Effect.setStyleProp(refElement, 'display', 'none');
		Spry.Effect.setStyleProp(refElement, 'visibility', visibilityOrig);
	}
	return styleProp;
};

Spry.Effect.makePositioned = function(element)
{
	var pos = Spry.Effect.getStyleProp(element, 'position');
	if (!pos || pos == 'static')
	{
		element.style.position = 'relative';

		// Opera returns the offset relative to the positioning context, when an
		// element is position relative but top and left have not been defined
		if (window.opera)
		{
			element.style.top = 0;
			element.style.left = 0;
		}
	}
};

Spry.Effect.isInvisible = function(element)
{
	var propDisplay = Spry.Effect.getStyleProp(element, 'display');
	if (propDisplay && propDisplay.toLowerCase() == 'none')
		return true;

	var propVisible = Spry.Effect.getStyleProp(element, 'visibility');
	if (propVisible && propVisible.toLowerCase() == 'hidden')
		return true;

	return false;
};

Spry.Effect.enforceVisible = function(element)
{
	var propDisplay = Spry.Effect.getStyleProp(element, 'display');
	if (propDisplay && propDisplay.toLowerCase() == 'none')
		Spry.Effect.setStyleProp(element, 'display', 'block');

	var propVisible = Spry.Effect.getStyleProp(element, 'visibility');
	if (propVisible && propVisible.toLowerCase() == 'hidden')
		Spry.Effect.setStyleProp(element, 'visibility', 'visible');
};

Spry.Effect.makeClipping = function(element)
{
	var overflow = Spry.Effect.getStyleProp(element, 'overflow');
	if (!overflow || (overflow.toLowerCase() != 'hidden' && overflow.toLowerCase() != 'scroll'))
	{
		// IE 7 bug: set overflow property to hidden changes the element height to 0
		// -> therefore we save the height before changing the overflow property and set the old size back
		var heightCache = 0;
		var needsCache = /MSIE 7.0/.test(navigator.userAgent) && /Windows NT/.test(navigator.userAgent);
		if(needsCache)
			heightCache = Spry.Effect.getDimensionsRegardlessOfDisplayState(element).height;

		Spry.Effect.setStyleProp(element, 'overflow', 'hidden');

		if(needsCache)
			Spry.Effect.setStyleProp(element, 'height', heightCache+'px');
	}
};

Spry.Effect.cleanWhitespace = function(element) 
{
	var childCountInit = element.childNodes.length;
  for (var i = childCountInit - 1; i >= 0; i--) {
  	var node = element.childNodes[i];
		if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
			try
			{
				element.removeChild(node);
			}
			catch (e) {Spry.Effect.Utils.showError('Spry.Effect.cleanWhitespace: ' + e);}
	}
};

Spry.Effect.getComputedStyle = function(element)
{
	return /MSIE/.test(navigator.userAgent) ? element.currentStyle : document.defaultView.getComputedStyle(element, null);
};

Spry.Effect.getDimensions = function(element)
{
	var dimensions = new Spry.Effect.Utils.Rectangle;
	var computedStyle = null;

	if (element.style.width && /px/i.test(element.style.width))
		dimensions.width = parseInt(element.style.width, 10); // without padding
	else
	{
		computedStyle = Spry.Effect.getComputedStyle(element);
		var tryComputedStyle = computedStyle && computedStyle.width && /px/i.test(computedStyle.width);

		if (tryComputedStyle)
			dimensions.width = parseInt(computedStyle.width, 10); // without padding, includes css

		if (!tryComputedStyle || dimensions.width == 0) // otherwise we might run into problems on safari and opera (mac only)
			dimensions.width = element.offsetWidth;   // includes padding
	}

	if (element.style.height && /px/i.test(element.style.height))
		dimensions.height = parseInt(element.style.height, 10); // without padding
	else
	{
		if (!computedStyle)
			computedStyle = Spry.Effect.getComputedStyle(element);

		var tryComputedStyle = computedStyle && computedStyle.height && /px/i.test(computedStyle.height);

		if (tryComputedStyle)
			dimensions.height = parseInt(computedStyle.height, 10); // without padding, includes css

		if(!tryComputedStyle || dimensions.height == 0) // otherwise we might run into problems on safari and opera (mac only)
			dimensions.height = element.offsetHeight;   // includes padding
	}
	return dimensions;
};

Spry.Effect.getDimensionsRegardlessOfDisplayState = function(element, displayElement)
{
	// If the displayElement display property is set to 'none', we temporarily set its
	// visibility state to 'hidden' to be able to calculate the dimension.

	var refElement = displayElement ? displayElement : element;
	var displayOrig = Spry.Effect.getStyleProp(refElement, 'display');
	var visibilityOrig = Spry.Effect.getStyleProp(refElement, 'visibility');

	if(displayOrig == 'none')
	{
		Spry.Effect.setStyleProp(refElement, 'visibility', 'hidden');
		Spry.Effect.setStyleProp(refElement, 'display', 'block');

		if(window.opera) // opera needs focus to calculate the size for hidden elements
			refElement.focus();
	}

	var dimensions = Spry.Effect.getDimensions(element);

	if(displayOrig == 'none') // reset the original values
	{
		Spry.Effect.setStyleProp(refElement, 'display', 'none');
		Spry.Effect.setStyleProp(refElement, 'visibility', visibilityOrig);
	}
	return dimensions;
};

Spry.Effect.getOpacity = function(element)
{
  var o = Spry.Effect.getStyleProp(element, "opacity");
  if (typeof o == 'undefined' || o == null)
    o = 1.0;
  return o;
};

Spry.Effect.getBgColor = function(ele)
{
  return Spry.Effect.getStyleProp(ele, "background-color");
};

Spry.Effect.intPropStyle = function(e, prop){
		var i = parseInt(Spry.Effect.getStyleProp(e, prop), 10);
		if (isNaN(i))
			return 0;
		return i;
};

Spry.Effect.getPosition = function(element)
{
	var position = new Spry.Effect.Utils.Position;
	var computedStyle = null;

	if (element.style.left  && /px/i.test(element.style.left))
		position.x = parseInt(element.style.left, 10); // without padding
	else
	{
		computedStyle = Spry.Effect.getComputedStyle(element);
		var tryComputedStyle = computedStyle && computedStyle.left && /px/i.test(computedStyle.left);

		if (tryComputedStyle)
			position.x = parseInt(computedStyle.left, 10); // without padding, includes css

		if(!tryComputedStyle || position.x == 0) // otherwise we might run into problems on safari and opera (mac only)
			position.x = element.offsetLeft;   // includes padding
	}

	if (element.style.top && /px/i.test(element.style.top))
		position.y = parseInt(element.style.top, 10); // without padding
	else
	{
		if (!computedStyle)
			computedStyle = Spry.Effect.getComputedStyle(element);

    var tryComputedStyle = computedStyle && computedStyle.top && /px/i.test(computedStyle.top);

		if (tryComputedStyle)
			position.y = parseInt(computedStyle.top, 10); // without padding, includes css

		if(!tryComputedStyle || position.y == 0) // otherwise we might run into problems on safari and opera (mac only)
			position.y = element.offsetTop;   // includes padding
	}
	return position;
};

Spry.Effect.getOffsetPosition = Spry.Effect.getPosition; // deprecated

//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Animator
// (base class)
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Animator = function(options)
{
	Spry.Utils.Notifier.call(this);
	
	this.name = 'Animator';
	this.element = null;
	this.startMilliseconds = 0;
	this.repeat = 'none';
	this.isRunning = false;
	this.timer = null;
	this.cancelRemaining = 0;

	if (!options)
		var options = {};

	if (options.toggle)
		this.direction = false;
	else
		this.direction = Spry.forwards;
	
	var self = this;
	if (options.setup != null)
		this.addObserver({onPreEffect: function(){try{self.options.setup(self.element, self);}catch(e){Spry.Effect.Utils.showError('Spry.Effect.Animator.prototype.start: setup callback: ' + e);}}});

	if (options.finish != null)
		this.addObserver({onPostEffect: function(){try{self.options.finish(self.element, self);}catch(e){Spry.Effect.Utils.showError('Spry.Effect.Animator.prototype.stop: finish callback: ' + e);}}});

	this.options = {
		duration: 1000,
		toggle: false,
		transition: Spry.linearTransition,
		interval: 16 // ca. 62 fps
	};

	this.setOptions(options);
	if (options.transition)
		this.setTransition(options.transition);

	if (options.fps)
		this.setFps(options.fps);
};
Spry.Effect.Animator.prototype = new Spry.Utils.Notifier();
Spry.Effect.Animator.prototype.constructor = Spry.Utils.Animator;

Spry.Effect.Animator.prototype.notStaticAnimator = true;

Spry.Effect.Animator.prototype.setOptions = function(options)
{
	if (!options)
		return;
	for (var prop in options)
		this.options[prop] = options[prop];
};
Spry.Effect.Animator.prototype.setTransition = function(transition){
	if (typeof transition == 'number' || transition == "1" || transition == "2")
		switch (parseInt(transition,10))
		{
			case 1: transition = Spry.linearTransition; break;
			case 2: transition = Spry.sinusoidalTransition; break;
			default: Spry.Effect.Utils.showError('unknown transition');
		}

	else if (typeof transition == 'string')
	{
		if (typeof window[transition] == 'function')
			transition = window[transition];
		else if (typeof Spry[transition] == 'function')
			transition = Spry[transition];
		else
			Spry.Effect.Utils.showError('unknown transition');
	}

	this.options.transition = transition;
	if (typeof this.effectsArray != 'undefined'){
		var l = this.effectsArray.length;
		for (var i = 0; i < l; i++)
				this.effectsArray[i].effect.setTransition(transition);
	}
};

Spry.Effect.Animator.prototype.setDuration = function(duration){
	this.options.duration = duration;
	if (typeof this.effectsArray != 'undefined')
	{
		var l = this.effectsArray.length;
		for (var i = 0; i < l; i++)
		{
			this.effectsArray[i].effect.setDuration(duration);
		}
	}
};

Spry.Effect.Animator.prototype.setFps = function(fps){
	this.options.interval = parseInt(1000 / fps, 10);
	this.options.fps = fps;
	if (typeof this.effectsArray != 'undefined')
	{
		var l = this.effectsArray.length;
		for (var i = 0; i < l; i++)
		{
			this.effectsArray[i].effect.setFps(fps);
		}
	}
};

Spry.Effect.Animator.prototype.start = function(withoutTimer)
{
	if (!this.element)
		return;

	if (arguments.length == 0)
		withoutTimer = false;

	if (this.isRunning)
		this.cancel();

	this.prepareStart();
	var currDate = new Date();
	this.startMilliseconds = currDate.getTime();

	if (this.element.id)
		this.element = document.getElementById(this.element.id);

	if (this.cancelRemaining != 0 && this.options.toggle)
	{
		if (this.cancelRemaining < 1 && typeof this.options.transition == 'function')
		{
			var startTime = 0;
			var stopTime = this.options.duration;
			var start = 0;
			var stop = 1;
			var emergency = 0;
			this.cancelRemaining = Math.round(this.cancelRemaining * 1000) / 1000;
			var found = false;
			var middle = 0;
			while (!found)
			{
				if (emergency++ > this.options.duration) break;
				var half = startTime + ((stopTime - startTime) / 2);
				middle = Math.round(this.options.transition(half, 1, -1, this.options.duration) * 1000) / 1000;
				if (middle == this.cancelRemaining)
				{
					this.startMilliseconds -= half;
					found = true;
				}
				if (middle < this.cancelRemaining)
				{
					stopTime = half;
					stop = middle;
				}
				else
				{
					startTime = half;
					start = middle;
				}
			}
		}
		this.cancelRemaining = 0;
	}
	this.notifyObservers('onPreEffect', this);

	if (withoutTimer == false)
	{
		var self = this;
		this.timer = setInterval(function() { self.drawEffect(); }, this.options.interval);
	}
	this.isRunning = true;
};
Spry.Effect.Animator.prototype.stopFlagReset = function()
{
	if (this.timer)
	{
		clearInterval(this.timer);
		this.timer = null;
	}
	this.startMilliseconds = 0;
};
Spry.Effect.Animator.prototype.stop = function()
{
	this.stopFlagReset();
	this.notifyObservers('onPostEffect', this);
	this.isRunning = false;
};

Spry.Effect.Animator.prototype.cancel = function()
{
	var elapsed = this.getElapsedMilliseconds();
	if (this.startMilliseconds > 0 && elapsed < this.options.duration)
		this.cancelRemaining = this.options.transition(elapsed, 0, 1, this.options.duration);

	this.stopFlagReset();
	this.notifyObservers('onCancel', this);
	this.isRunning = false;
};

Spry.Effect.Animator.prototype.drawEffect = function()
{
	var isRunning = true;

	this.notifyObservers('onStep', this);
	var timeElapsed = this.getElapsedMilliseconds();

	if (typeof this.options.transition != 'function'){
		Spry.Effect.Utils.showError('unknown transition');
		return;
	}
	this.animate();

	if (timeElapsed > this.options.duration)
	{
		isRunning = false;
		this.stop();
	}
	return isRunning;
};

Spry.Effect.Animator.prototype.getElapsedMilliseconds = function()
{
	if (this.startMilliseconds > 0)
	{
		var currDate = new Date();
		return (currDate.getTime() - this.startMilliseconds);
	}
	return 0;
};

Spry.Effect.Animator.prototype.doToggle = function()
{
	if (!this.direction)
	{
		this.direction = Spry.forwards;
		return;
	}
	if (this.options.toggle == true)
	{
		if (this.direction == Spry.forwards)
		{
			this.direction = Spry.backwards;
			this.notifyObservers('onToggle', this);
		} 
		else if (this.direction == Spry.backwards)
		{
			this.direction = Spry.forwards;
		}
	}
};

Spry.Effect.Animator.prototype.prepareStart = function()
{
		if (this.options && this.options.toggle)
			this.doToggle();
};

Spry.Effect.Animator.prototype.animate = function(){};
Spry.Effect.Animator.prototype.onStep = function(el)
{
	if (el != this)
		this.notifyObservers('onStep', this);
};
//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Move
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Move = function(element, fromPos, toPos, options)
{
	this.dynamicFromPos = false;
	if (arguments.length == 3)
	{
		options = toPos;
		toPos = fromPos;
		fromPos = Spry.Effect.getPosition(element);
		this.dynamicFromPos = true;
	}

	Spry.Effect.Animator.call(this, options);

	this.name = 'Move';
	this.element = Spry.Effect.getElement(element);
	if (!this.element)
		return;

	if (fromPos.units != toPos.units)
		Spry.Effect.Utils.showError('Spry.Effect.Move: Conflicting units (' + fromPos.units + ', ' + toPos.units + ')');

	this.units = fromPos.units;
	this.startX = Number(fromPos.x);
	this.stopX = Number(toPos.x);
	this.startY = Number(fromPos.y);
	this.stopY = Number(toPos.y);
};

Spry.Effect.Move.prototype = new Spry.Effect.Animator();
Spry.Effect.Move.prototype.constructor = Spry.Effect.Move;

Spry.Effect.Move.prototype.animate = function()
{
	var left = 0;
	var top = 0;
	var floor = Math.floor;
	var elapsed = this.getElapsedMilliseconds();
	if (this.direction == Spry.forwards)
	{
		left = floor(this.options.transition(elapsed, this.startX, this.stopX - this.startX, this.options.duration));
		top = floor(this.options.transition(elapsed, this.startY, this.stopY - this.startY, this.options.duration));
	}
	else if (this.direction == Spry.backwards)
	{
		left = floor(this.options.transition(elapsed, this.stopX, this.startX - this.stopX, this.options.duration));
		top = floor(this.options.transition(elapsed, this.stopY, this.startY - this.stopY, this.options.duration));
	}

	this.element.style.left = left + this.units;
	this.element.style.top = top + this.units;
};

Spry.Effect.Move.prototype.prepareStart = function()
{
	if (this.options && this.options.toggle)
		this.doToggle();

	if (this.dynamicFromPos == true)
	{
		var fromPos = Spry.Effect.getPosition(this.element);
		this.startX = fromPos.x;
		this.startY = fromPos.y;
		
		this.rangeMoveX = this.startX - this.stopX;
		this.rangeMoveY= this.startY - this.stopY;
	}
};

//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Size
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Size = function(element, fromRect, toRect, options)
{
	this.dynamicFromRect = false;

	if (arguments.length == 3)
	{
		options = toRect;
		toRect = fromRect;
		fromRect = Spry.Effect.getDimensionsRegardlessOfDisplayState(element);
		this.dynamicFromRect = true;
	}

	Spry.Effect.Animator.call(this, options);

	this.name = 'Size';
	this.element = Spry.Effect.getElement(element);
	if (!this.element)
		return;

	element = this.element;

	if (fromRect.units != toRect.units)
	{
		Spry.Effect.Utils.showError('Spry.Effect.Size: Conflicting units (' + fromRect.units + ', ' + toRect.units + ')');
		return false;
	}

	this.units = fromRect.units;

	var originalRect = Spry.Effect.getDimensionsRegardlessOfDisplayState(element);
	this.originalWidth = originalRect.width;
	this.originalHeight = originalRect.height;

	this.startWidth = fromRect.width;
	this.startHeight = fromRect.height;
	this.stopWidth = toRect.width;
	this.stopHeight = toRect.height;
	this.childImages = new Array();

	if (this.options.useCSSBox){
		Spry.Effect.makePositioned(this.element);
		var intProp = Spry.Effect.intPropStyle;
		this.startFromBorder_top = intProp(element, 'border-top-width');
		this.startFromBorder_bottom = intProp(element, 'border-bottom-width');
		this.startFromBorder_left = intProp(element, 'border-left-width');
		this.startFromBorder_right = intProp(element, 'border-right-width');
		this.startFromPadding_top = intProp(element, 'padding-top');
		this.startFromPadding_bottom = intProp(element, 'padding-bottom');
		this.startFromPadding_left = intProp(element, 'padding-left');
		this.startFromPadding_right = intProp(element, 'padding-right');
		this.startFromMargin_top = intProp(element, 'margin-top');
		this.startFromMargin_bottom = intProp(element, 'margin-bottom');
		this.startFromMargin_right = intProp(element, 'margin-right');
		this.startFromMargin_left = intProp(element, 'margin-left');
		this.startLeft = intProp(element, 'left');
		this.startTop = intProp(element, 'top');
	}

	if(this.options.scaleContent)
		Spry.Effect.Utils.fetchChildImages(element, this.childImages);

	this.fontFactor = 1.0;
	var fontSize = Spry.Effect.getStyleProp(this.element, 'font-size');
	if(fontSize && /em\s*$/.test(fontSize))
		this.fontFactor = parseFloat(fontSize);

	var isPercent = Spry.Effect.Utils.isPercentValue;

	if (isPercent(this.startWidth))
	{
		var startWidthPercent = Spry.Effect.Utils.getPercentValue(this.startWidth);
		this.startWidth = originalRect.width * (startWidthPercent / 100);
	}

	if (isPercent(this.startHeight))
	{
		var startHeightPercent = Spry.Effect.Utils.getPercentValue(this.startHeight);
		this.startHeight = originalRect.height * (startHeightPercent / 100);
	}

	if (isPercent(this.stopWidth))
	{
		var stopWidthPercent = Spry.Effect.Utils.getPercentValue(this.stopWidth);
		this.stopWidth = originalRect.width * (stopWidthPercent / 100);
	}

	if (isPercent(this.stopHeight))
	{
		var stopHeightPercent = Spry.Effect.Utils.getPercentValue(this.stopHeight);
		this.stopHeight = originalRect.height * (stopHeightPercent / 100);
	}

	this.enforceVisible = Spry.Effect.isInvisible(this.element);
};

Spry.Effect.Size.prototype = new Spry.Effect.Animator();
Spry.Effect.Size.prototype.constructor = Spry.Effect.Size;

Spry.Effect.Size.prototype.animate = function()
{
	var width = 0;
	var height = 0;
	var fontSize = 0;
	var direction = 0;
	var floor = Math.floor;
	var elapsed = this.getElapsedMilliseconds();

	if (this.direction == Spry.forwards) {
		width = floor(this.options.transition(elapsed, this.startWidth, this.stopWidth - this.startWidth, this.options.duration));
		height = floor(this.options.transition(elapsed, this.startHeight, this.stopHeight - this.startHeight, this.options.duration));
		direction = 1;
	} else if (this.direction == Spry.backwards) {
		width = floor(this.options.transition(elapsed, this.stopWidth, this.startWidth - this.stopWidth, this.options.duration));
		height = floor(this.options.transition(elapsed, this.stopHeight, this.startHeight - this.stopHeight, this.options.duration));
		direction = -1;
	}

	var propFactor = width/this.originalWidth;
	fontSize = this.fontFactor * propFactor;

	var elStyle = this.element.style;
	if (width < 0)
		width = 0;
	
	if (height < 0)
		height = 0;

	elStyle.width = width + this.units;
	elStyle.height = height + this.units;

	if (typeof this.options.useCSSBox != 'undefined' && this.options.useCSSBox == true)
	{
		var intProp = Spry.Effect.intPropStyle;
		var origTop = intProp(this.element, 'top');
		var origLeft = intProp(this.element, 'left');
		var origMarginTop = intProp(this.element, 'margin-top');
		var origMarginLeft = intProp(this.element, 'margin-left');

		var widthFactor = propFactor;
		var heightFactor = height / this.originalHeight;
		var border_top = floor(this.startFromBorder_top * heightFactor);
		var border_bottom = floor(this.startFromBorder_bottom * heightFactor);
		var border_left = floor(this.startFromBorder_left * widthFactor);
		var border_right = floor(this.startFromBorder_right * widthFactor);
		var padding_top = floor(this.startFromPadding_top * heightFactor);
		var padding_bottom = floor(this.startFromPadding_bottom * heightFactor);
		var padding_left = floor(this.startFromPadding_left * widthFactor);
		var padding_right = floor(this.startFromPadding_right * widthFactor);
		var margin_top = floor(this.startFromMargin_top * heightFactor);
		var margin_bottom = floor(this.startFromMargin_bottom * heightFactor);
		var margin_right = floor(this.startFromMargin_right * widthFactor);
		var margin_left = floor(this.startFromMargin_left * widthFactor);

		elStyle.borderTopWidth = border_top + this.units;
		elStyle.borderBottomWidth = border_bottom + this.units;
		elStyle.borderLeftWidth = border_left + this.units;
		elStyle.borderRightWidth = border_right + this.units;
		elStyle.paddingTop = padding_top + this.units;
		elStyle.paddingBottom = padding_bottom + this.units;
		elStyle.paddingLeft = padding_left + this.units;
		elStyle.paddingRight = padding_right + this.units;
		elStyle.marginTop  = margin_top + this.units;
		elStyle.marginBottom = margin_bottom + this.units;
		elStyle.marginLeft = margin_left + this.units;
		elStyle.marginRight = margin_right + this.units;

		// compensate the margin shrinking
		elStyle.left = floor(origLeft + origMarginLeft - margin_left) + this.units;
		elStyle.top = floor(origTop + origMarginTop - margin_top) + this.units;
	}

	if (this.options.scaleContent)
	{

		for(var i=0; i < this.childImages.length; i++)
		{
			this.childImages[i][0].style.width = propFactor * this.childImages[i][1] + this.units;
			this.childImages[i][0].style.height = propFactor * this.childImages[i][2] + this.units;
		}
		this.element.style.fontSize = fontSize + 'em';
	}

	if(this.enforceVisible)
	{
		Spry.Effect.enforceVisible(this.element);
		this.enforceVisible = false;
	}
};

Spry.Effect.Size.prototype.prepareStart = function()
{
	if (this.options && this.options.toggle)
		this.doToggle();	

	if (this.dynamicFromRect == true)
	{
		var fromRect = Spry.Effect.getDimensions(this.element);
		this.startWidth = fromRect.width;
		this.startHeight = fromRect.height;

		this.widthRange = this.startWidth - this.stopWidth;
		this.heightRange = this.startHeight - this.stopHeight;
	}
};

//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Opacity
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Opacity = function(element, startOpacity, stopOpacity, options)
{
	this.dynamicStartOpacity = false;
	if (arguments.length == 3)
	{
		options = stopOpacity;
		stopOpacity = startOpacity;
		startOpacity = Spry.Effect.getOpacity(element);
		this.dynamicStartOpacity = true;
	}

	Spry.Effect.Animator.call(this, options);

	this.name = 'Opacity';
	this.element = Spry.Effect.getElement(element);
	if (!this.element)
		return;

 	// make this work on IE on elements without 'layout'
	if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
		Spry.Effect.setStyleProp(this.element, 'zoom', '1');

	this.startOpacity = startOpacity;
	this.stopOpacity = stopOpacity;
	this.enforceVisible = Spry.Effect.isInvisible(this.element);
};

Spry.Effect.Opacity.prototype = new Spry.Effect.Animator();
Spry.Effect.Opacity.prototype.constructor = Spry.Effect.Opacity;

Spry.Effect.Opacity.prototype.animate = function()
{
	var opacity = 0;
	var elapsed = this.getElapsedMilliseconds();
	if (this.direction == Spry.forwards) 
		opacity = this.options.transition(elapsed, this.startOpacity, this.stopOpacity - this.startOpacity, this.options.duration);
	else if (this.direction == Spry.backwards) 
		opacity = this.options.transition(elapsed, this.stopOpacity, this.startOpacity - this.stopOpacity, this.options.duration);

	if (opacity < 0)
		opacity = 0;

	if(/MSIE/.test(navigator.userAgent))
	{
		var tmpval = Spry.Effect.getStyleProp(this.element,'filter');
		if (tmpval){
			tmpval = tmpval.replace(/alpha\(opacity=[0-9]{1,3}\)/g, '');
		}
		this.element.style.filter = tmpval + "alpha(opacity=" + Math.floor(opacity * 100) + ")";
	}
	else
		this.element.style.opacity = opacity;

	if(this.enforceVisible)
	{
		Spry.Effect.enforceVisible(this.element);
		this.enforceVisible = false;
	}
};

Spry.Effect.Opacity.prototype.prepareStart = function()
{
	if (this.options && this.options.toggle)
		this.doToggle();	

	if (this.dynamicStartOpacity == true)
	{
		this.startOpacity = Spry.Effect.getOpacity(this.element);
		this.opacityRange = this.startOpacity - this.stopOpacity;
	}
};

//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Color
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Color = function(element, startColor, stopColor, options)
{
	this.dynamicStartColor = false;
	if (arguments.length == 3)
	{
		options = stopColor;
		stopColor = startColor;
		startColor = Spry.Effect.getBgColor(element);
		this.dynamicStartColor = true;
	}
	
	Spry.Effect.Animator.call(this, options);

	this.name = 'Color';
	this.element = Spry.Effect.getElement(element);
	if (!this.element)
		return;

	this.startColor = startColor;
	this.stopColor = stopColor;
	this.startRedColor = Spry.Effect.Utils.hexToInt(startColor.substr(1,2));
	this.startGreenColor = Spry.Effect.Utils.hexToInt(startColor.substr(3,2));
	this.startBlueColor = Spry.Effect.Utils.hexToInt(startColor.substr(5,2));
	this.stopRedColor = Spry.Effect.Utils.hexToInt(stopColor.substr(1,2));
	this.stopGreenColor = Spry.Effect.Utils.hexToInt(stopColor.substr(3,2));
	this.stopBlueColor = Spry.Effect.Utils.hexToInt(stopColor.substr(5,2));
};

Spry.Effect.Color.prototype = new Spry.Effect.Animator();
Spry.Effect.Color.prototype.constructor = Spry.Effect.Color;

Spry.Effect.Color.prototype.animate = function()
{
	var redColor = 0;
	var greenColor = 0;
	var blueColor = 0;
	var floor = Math.floor;
	var elapsed = this.getElapsedMilliseconds();

	if (this.direction == Spry.forwards)
	{
		redColor = floor(this.options.transition(elapsed, this.startRedColor, this.stopRedColor - this.startRedColor, this.options.duration));
		greenColor = floor(this.options.transition(elapsed, this.startGreenColor, this.stopGreenColor - this.startGreenColor, this.options.duration));
		blueColor = floor(this.options.transition(elapsed, this.startBlueColor, this.stopBlueColor - this.startBlueColor, this.options.duration));
	}
	else if (this.direction == Spry.backwards)
	{
		redColor = floor(this.options.transition(elapsed, this.stopRedColor, this.startRedColor - this.stopRedColor, this.options.duration));
		greenColor = floor(this.options.transition(elapsed, this.stopGreenColor, this.startGreenColor - this.stopGreenColor, this.options.duration));
		blueColor = floor(this.options.transition(elapsed, this.stopBlueColor, this.startBlueColor - this.stopBlueColor, this.options.duration));
	}

	this.element.style.backgroundColor = Spry.Effect.Utils.rgb(redColor, greenColor, blueColor);
};

Spry.Effect.Color.prototype.prepareStart = function() 
{
	if (this.options && this.options.toggle)
		this.doToggle();

	if (this.dynamicStartColor == true)
	{
		this.startColor = Spry.Effect.getBgColor(element);
		this.startRedColor = Spry.Effect.Utils.hexToInt(startColor.substr(1,2));
		this.startGreenColor = Spry.Effect.Utils.hexToInt(startColor.substr(3,2));
		this.startBlueColor = Spry.Effect.Utils.hexToInt(startColor.substr(5,2));
		this.redColorRange = this.startRedColor - this.stopRedColor;
		this.greenColorRange = this.startGreenColor - this.stopGreenColor;
		this.blueColorRange = this.startBlueColor - this.stopBlueColor;
	}
};

//////////////////////////////////////////////////////////////////////
//
// Spry.Effect.Cluster
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Cluster = function(options)
{
	Spry.Effect.Animator.call(this, options);

	this.name = 'Cluster';
	this.effectsArray = new Array();
	this.currIdx = -1;
	var _ClusteredEffect = function(effect, kind)
	{
		this.effect = effect;
		this.kind = kind; // "parallel" or "queue"
		this.isRunning = false;
	};

	this.ClusteredEffect = _ClusteredEffect;
};

Spry.Effect.Cluster.prototype = new Spry.Effect.Animator();
Spry.Effect.Cluster.prototype.constructor = Spry.Effect.Cluster;

Spry.Effect.Cluster.prototype.setInterval = function(interval){
	var l = this.effectsArray.length;
	this.options.interval = interval;
	for (var i = 0; i < l; i++)
	{
		this.effectsArray[i].effect.setInterval(interval);
	}
};
Spry.Effect.Cluster.prototype.drawEffect = function()
{
	var isRunning = true;
	var allEffectsDidRun = false;
	var baseEffectIsStillRunning = false;
	var evalNextEffectsRunning = false;

	if ((this.currIdx == -1 && this.direction == Spry.forwards) || (this.currIdx == this.effectsArray.length && this.direction == Spry.backwards))
		this.initNextEffectsRunning();

	var start = this.direction == Spry.forwards ? 0 : this.effectsArray.length-1;
	var stop = this.direction == Spry.forwards ? this.effectsArray.length : -1;
	var step = this.direction == Spry.forwards ? 1 : -1;
	for (var i = start; i != stop; i+=step)
	{
		if (this.effectsArray[i].isRunning == true)
		{
			baseEffectIsStillRunning = this.effectsArray[i].effect.drawEffect();
			if (baseEffectIsStillRunning == false && i == this.currIdx)
			{
				this.effectsArray[i].isRunning = false;
				evalNextEffectsRunning = true;
			}
		}
	}

	if (evalNextEffectsRunning == true)
		allEffectsDidRun = this.initNextEffectsRunning();

	if (allEffectsDidRun == true) {
		this.stop();
		isRunning = false;
		for (var i = 0; i < this.effectsArray.length; i++)
			this.effectsArray[i].isRunning = false;

		this.currIdx = this.direction == Spry.forwards ? this.effectsArray.length: -1;
	}
	return isRunning;
};

Spry.Effect.Cluster.prototype.initNextEffectsRunning = function()
{
	var allEffectsDidRun = false;
	var step = this.direction == Spry.forwards ? 1 : -1;
	var stop = this.direction == Spry.forwards ? this.effectsArray.length : -1;
	this.currIdx+=step;
	if ( (this.currIdx > (this.effectsArray.length - 1) && this.direction == Spry.forwards) || (this.currIdx < 0 && this.direction == Spry.backwards))
		allEffectsDidRun = true;
	else
		for (var i = this.currIdx; i != stop; i+=step)
		{
			if ((i > this.currIdx && this.direction == Spry.forwards || i < this.currIdx && this.direction == Spry.backwards) && this.effectsArray[i].kind == "queue")
				break;
			this.effectsArray[i].effect.start(true);
			this.effectsArray[i].isRunning = true;
			this.currIdx = i;
		}

	return allEffectsDidRun;
};

Spry.Effect.Cluster.prototype.toggleCluster = function()
{
	if (!this.direction)
	{
		this.direction = Spry.forwards;
		return;
	}

	if (this.options.toggle == true)
	{
		if (this.direction == Spry.forwards)
		{
			this.direction = Spry.backwards;
			this.notifyObservers('onToggle', this);
			this.currIdx = this.effectsArray.length;
		}
		else if (this.direction == Spry.backwards)
		{
			this.direction = Spry.forwards;
			this.currIdx = -1;
		}
	}
	else
	{
		if (this.direction == Spry.forwards)
			this.currIdx = -1;
		else if (this.direction == Spry.backwards)
			this.currIdx = this.effectsArray.length;
	}
};

Spry.Effect.Cluster.prototype.doToggle = function()
{
	this.toggleCluster();

	// toggle all effects of the cluster, too
	for (var i = 0; i < this.effectsArray.length; i++)
	{
		if (this.effectsArray[i].effect.options && (this.effectsArray[i].effect.options.toggle != null))
			if (this.effectsArray[i].effect.options.toggle == true)
				this.effectsArray[i].effect.doToggle();
	}
};

Spry.Effect.Cluster.prototype.cancel = function()
{
	for (var i = 0; i < this.effectsArray.length; i++)
		if (this.effectsArray[i].effect.isRunning)
			this.effectsArray[i].effect.cancel();
	
	var elapsed = this.getElapsedMilliseconds();
	if (this.startMilliseconds > 0 && elapsed < this.options.duration)
		this.cancelRemaining = this.options.transition(elapsed, 0, 1, this.options.duration);
	this.stopFlagReset();
	this.notifyObservers('onCancel', this);
	this.isRunning = false;
};

Spry.Effect.Cluster.prototype.addNextEffect = function(effect)
{
	effect.addObserver(this);
	this.effectsArray[this.effectsArray.length] = new this.ClusteredEffect(effect, "queue");
	if (this.effectsArray.length == 1)
	{
		// with the first added effect we know the element
		// that the cluster is working on
		this.element = effect.element;
	}
};

Spry.Effect.Cluster.prototype.addParallelEffect = function(effect)
{
	if (this.effectsArray.length == 0 || this.effectsArray[this.effectsArray.length-1].kind != 'parallel')
		effect.addObserver(this);

	this.effectsArray[this.effectsArray.length] = new this.ClusteredEffect(effect, "parallel");
	if (this.effectsArray.length == 1)
	{
		// with the first added effect we know the element
		// that the cluster is working on
		this.element = effect.element;
	}
};

Spry.Effect.Cluster.prototype.prepareStart = function()
{
	this.toggleCluster();
};

//////////////////////////////////////////////////////////////////////
//
// Combination effects
// Custom effects can be build by combining basic effect bahaviour
// like Move, Size, Color, Opacity
//
//////////////////////////////////////////////////////////////////////

Spry.Effect.Fade = function (element, options) 
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Fade');

	Spry.Effect.Cluster.call(this, options);

	this.name = 'Fade';
	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;
	var durationInMilliseconds = 1000;
	var fromOpacity = 0.0;
	var toOpacity = 100.0;
	var doToggle = false;
	var transition = Spry.fifthTransition;
	var fps = 60;
	var originalOpacity = 0;
	if(/MSIE/.test(navigator.userAgent))
		originalOpacity = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(this.element, 'filter').replace(/alpha\(opacity=([0-9]{1,3})\)/g, '$1'), 10);
	else
		originalOpacity = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(this.element, 'opacity') * 100, 10);

	if (isNaN(originalOpacity))
		originalOpacity = 100;

	if (options)
	{
		if (options.duration != null) durationInMilliseconds = options.duration;
		if (options.from != null){
			if (Spry.Effect.Utils.isPercentValue(options.from))
				fromOpacity = Spry.Effect.Utils.getPercentValue(options.from) * originalOpacity / 100;
			else
				fromOpacity = options.from;
		}
		if (options.to != null)
		{	
			if (Spry.Effect.Utils.isPercentValue(options.to))
				toOpacity = Spry.Effect.Utils.getPercentValue(options.to) * originalOpacity / 100;
			else
				toOpacity = options.to;
		}
		if (options.toggle != null) doToggle = options.toggle;
		if (options.transition != null) transition = options.transition;
		if (options.fps != null) fps = options.fps;
		else this.options.transition = transition;
	}

	fromOpacity = fromOpacity/ 100.0;
	toOpacity = toOpacity / 100.0;

	options = {duration: durationInMilliseconds, toggle: doToggle, transition: transition, from: fromOpacity, to: toOpacity, fps: fps};
	var fadeEffect = new Spry.Effect.Opacity(element, fromOpacity, toOpacity, options);
	this.addNextEffect(fadeEffect);
};

Spry.Effect.Fade.prototype = new Spry.Effect.Cluster();
Spry.Effect.Fade.prototype.constructor = Spry.Effect.Fade;

Spry.Effect.Blind = function (element, options)
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Blind'); 

	Spry.Effect.Cluster.call(this, options);

	this.name = 'Blind';
	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;
	var durationInMilliseconds = 1000;
	var doToggle = false;
	var kindOfTransition = Spry.circleTransition;
	var fps = 60;
	var doScaleContent = false;

	Spry.Effect.makeClipping(element);

	var originalRect = Spry.Effect.getDimensionsRegardlessOfDisplayState(element);
	var fromHeightPx  = originalRect.height;
	var toHeightPx    = 0;
	var optionFrom = options ? options.from : originalRect.height;
	var optionTo   = options ? options.to : 0;
	var fullCSSBox = false;


	if (options)
	{
		if (options.duration != null) durationInMilliseconds = options.duration;
		if (options.from != null)
		{
			if (Spry.Effect.Utils.isPercentValue(options.from))
				fromHeightPx = Spry.Effect.Utils.getPercentValue(options.from) * originalRect.height / 100;
			else
				fromHeightPx = Spry.Effect.Utils.getPixelValue(options.from);
		}
		if (options.to != null)
		{
			if (Spry.Effect.Utils.isPercentValue(options.to))
				toHeightPx = Spry.Effect.Utils.getPercentValue(options.to) * originalRect.height / 100;
			else
				toHeightPx = Spry.Effect.Utils.getPixelValue(options.to);
		}
		if (options.toggle != null) doToggle = options.toggle;
		if (options.transition != null) kindOfTransition = options.transition;
		if (options.fps != null) fps = options.fps;
		if (options.useCSSBox != null) fullCSSBox = options.useCSSBox;
	}

	var fromRect = new Spry.Effect.Utils.Rectangle;
	fromRect.width = originalRect.width;
	fromRect.height = fromHeightPx;

	var toRect = new Spry.Effect.Utils.Rectangle;
	toRect.width = originalRect.width;
	toRect.height = toHeightPx;

	options = {duration:durationInMilliseconds, toggle:doToggle, transition:kindOfTransition, scaleContent:doScaleContent, useCSSBox: fullCSSBox, from: optionFrom, to: optionTo, fps: fps};
	var blindEffect = new Spry.Effect.Size(element, fromRect, toRect, options);
	this.addNextEffect(blindEffect);
};

Spry.Effect.Blind.prototype = new Spry.Effect.Cluster();
Spry.Effect.Blind.prototype.constructor = Spry.Effect.Blind;

Spry.Effect.Highlight = function (element, options) 
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Highlight'); 

	Spry.Effect.Cluster.call(this, options);

	this.name = 'Highlight';
	var durationInMilliseconds = 1000;
	var toColor = "#ffffff";
	var doToggle = false;
	var kindOfTransition = Spry.sinusoidalTransition;
	var fps = 60;
	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;
	var fromColor = Spry.Effect.getBgColor(element);
	if (fromColor == "transparent") fromColor = "#ffff99";

	if (options)
	{
		if (options.duration != null) durationInMilliseconds = options.duration;
		if (options.from != null) fromColor = options.from;
		if (options.to != null) toColor = options.to;
		if (options.toggle != null) doToggle = options.toggle;
		if (options.transition != null) kindOfTransition = options.transition;
		if (options.fps != null) fps = options.fps;
	}

	if ( fromColor.indexOf('rgb') != -1 )
		var fromColor = Spry.Effect.Utils.rgb(parseInt(fromColor.substring(fromColor.indexOf('(')+1, fromColor.indexOf(',')),10), parseInt(fromColor.substring(fromColor.indexOf(',')+1, fromColor.lastIndexOf(',')),10), parseInt(fromColor.substring(fromColor.lastIndexOf(',')+1, fromColor.indexOf(')')),10));

	if ( toColor.indexOf('rgb') != -1 )
		var toColor = Spry.Effect.Utils.rgb(parseInt(toColor.substring(toColor.indexOf('(')+1, toColor.indexOf(',')),10), parseInt(toColor.substring(toColor.indexOf(',')+1, toColor.lastIndexOf(',')),10), parseInt(toColor.substring(toColor.lastIndexOf(',')+1, toColor.indexOf(')')),10));

	var fromColor = Spry.Effect.Utils.longColorVersion(fromColor);
	var toColor = Spry.Effect.Utils.longColorVersion(toColor);

	this.restoreBackgroundImage = Spry.Effect.getStyleProp(element, 'background-image');

	options = {duration: durationInMilliseconds, toggle: doToggle, transition: kindOfTransition, fps: fps};
	var highlightEffect = new Spry.Effect.Color(element, fromColor, toColor, options);
	this.addNextEffect(highlightEffect);

	this.addObserver({
		onPreEffect:
		function(effect){
			Spry.Effect.setStyleProp(effect.element, 'background-image', 'none');
		},
		onPostEffect:
		function(effect){
			Spry.Effect.setStyleProp(effect.element, 'background-image', effect.restoreBackgroundImage);

			if (effect.direction == Spry.forwards && effect.options.restoreColor)
				Spry.Effect.setStyleProp(element, 'background-color', effect.options.restoreColor);		
		}
	});
};

Spry.Effect.Highlight.prototype = new Spry.Effect.Cluster();
Spry.Effect.Highlight.prototype.constructor = Spry.Effect.Highlight;

Spry.Effect.Slide = function (element, options) 
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Slide'); 

	Spry.Effect.Cluster.call(this, options);

	this.name = 'Slide';
	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;
	var durationInMilliseconds = 1000;
	var doToggle = false;
	var kindOfTransition = Spry.sinusoidalTransition;
	var fps = 60;
	var slideHorizontally = false;
	var firstChildElt = Spry.Effect.Utils.getFirstChildElement(element);
	var direction = -1;

	// IE 7 does not clip static positioned elements -> make element position relative
	if(/MSIE 7.0/.test(navigator.userAgent) && /Windows NT/.test(navigator.userAgent))
		Spry.Effect.makePositioned(element);

	Spry.Effect.makeClipping(element);

	// for IE 6 on win: check if position is static or fixed -> not supported and would cause trouble
	if(/MSIE 6.0/.test(navigator.userAgent) && /Windows NT/.test(navigator.userAgent))
	{
		var pos = Spry.Effect.getStyleProp(element, 'position');
		if(pos && (pos == 'static' || pos == 'fixed'))
		{
			Spry.Effect.setStyleProp(element, 'position', 'relative');
			Spry.Effect.setStyleProp(element, 'top', '');
			Spry.Effect.setStyleProp(element, 'left', '');
		}
	}

	if(firstChildElt)
	{
		Spry.Effect.makePositioned(firstChildElt);
		Spry.Effect.makeClipping(firstChildElt);
  
		var childRect = Spry.Effect.getDimensionsRegardlessOfDisplayState(firstChildElt, element);
		Spry.Effect.setStyleProp(firstChildElt, 'width', childRect.width + 'px');
	}

	var fromDim = Spry.Effect.getDimensionsRegardlessOfDisplayState(element);

	var initDim = new Spry.Effect.Utils.Rectangle();
	var toDim = new Spry.Effect.Utils.Rectangle();
	initDim.width = toDim.width = fromDim.width;
	initDim.height = toDim.height = fromDim.height;

	if (!this.options.to){
		if (!options)
			options = {};

		options.to = '0%';
	}

	if (options && options.horizontal !== null && options.horizontal === true)
		slideHorizontally = true;

	if (options.duration != null) durationInMilliseconds = options.duration;

	if (options.from != null)
	{
		if(slideHorizontally)
		{
				if (Spry.Effect.Utils.isPercentValue(options.from))
					fromDim.width = initDim.width * Spry.Effect.Utils.getPercentValue(options.from) / 100;
				else
					fromDim.width = Spry.Effect.Utils.getPixelValue(options.from);
		}
		else
		{
				if (Spry.Effect.Utils.isPercentValue(options.from))
					fromDim.height = initDim.height * Spry.Effect.Utils.getPercentValue(options.from) / 100;
				else
					fromDim.height = Spry.Effect.Utils.getPixelValue(options.from);
		}
	}

	if (options.to != null)
	{
			if(slideHorizontally)
			{
				if (Spry.Effect.Utils.isPercentValue(options.to))
					toDim.width = initDim.width * Spry.Effect.Utils.getPercentValue(options.to) / 100;
				else
					toDim.width = Spry.Effect.Utils.getPixelValue(options.to);
			}
			else
			{
				if (Spry.Effect.Utils.isPercentValue(options.to))
					toDim.height = initDim.height * Spry.Effect.Utils.getPercentValue(options.to) / 100;
				else
					toDim.height = Spry.Effect.Utils.getPixelValue(options.to);
		}
	}
	if (options.toggle != null) doToggle = options.toggle;
	if (options.transition != null) kindOfTransition = options.transition;
	if (options.fps != null) fps = options.fps;

	options = {duration: durationInMilliseconds, transition: kindOfTransition, scaleContent: false, toggle:doToggle, fps: fps};
	var size = new Spry.Effect.Size(element, fromDim, toDim, options);
	this.addParallelEffect(size);

	if ( (fromDim.width < toDim.width && slideHorizontally) || (fromDim.height < toDim.height && !slideHorizontally))
		direction = 1;
	
	var fromPos = new Spry.Effect.Utils.Position();
	var toPos = new Spry.Effect.Utils.Position();
	toPos.x = fromPos.x = Spry.Effect.intPropStyle(firstChildElt, 'left');
	toPos.y = fromPos.y = Spry.Effect.intPropStyle(firstChildElt, 'top');
	toPos.units = fromPos.units;

	if (slideHorizontally)
		toPos.x = parseInt(fromPos.x + direction * (fromDim.width - toDim.width), 10);
	else
		toPos.y = parseInt(fromPos.y + direction * (fromDim.height - toDim.height), 10);

	if (direction == 1){
		var tmp = fromPos;
		var fromPos = toPos;
		var toPos = tmp;
	}

	options = {duration: durationInMilliseconds, transition: kindOfTransition, toggle:doToggle, from: fromPos, to: toPos, fps: fps};
	var move = new Spry.Effect.Move(firstChildElt, fromPos, toPos, options);
	this.addParallelEffect(move);
};

Spry.Effect.Slide.prototype = new Spry.Effect.Cluster();
Spry.Effect.Slide.prototype.constructor = Spry.Effect.Slide;

Spry.Effect.Grow = function (element, options) 
{
	if (!element)
		return;
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Grow');

	Spry.Effect.Cluster.call(this, options);

	this.name = 'Grow';
	var durationInMilliseconds = 1000;
	var doToggle = false;
	var doScaleContent = true;
	var calcHeight = false;
	var growFromCenter = true;
	var fullCSSBox = false;
	var kindOfTransition = Spry.squareTransition;
	var fps = 60;
	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;

	Spry.Effect.makeClipping(element);

	var dimRect = Spry.Effect.getDimensionsRegardlessOfDisplayState(element);
	var originalWidth = dimRect.width;
	var originalHeight = dimRect.height;
	var propFactor = (originalWidth == 0) ? 1 :originalHeight/originalWidth;

	var fromRect = new Spry.Effect.Utils.Rectangle;
	fromRect.width = 0;
	fromRect.height = 0;

	var toRect = new Spry.Effect.Utils.Rectangle;
	toRect.width = originalWidth;
	toRect.height = originalHeight;

	var optionFrom = options ? options.from : dimRect.width;
	var optionTo   = options ? options.to : 0;
	var pixelValue = Spry.Effect.Utils.getPixelValue;

	if (options)
	{
		if (options.growCenter != null) growFromCenter = options.growCenter;
		if (options.duration != null) durationInMilliseconds = options.duration;
		if (options.useCSSBox != null) fullCSSBox = options.useCSSBox;
		if (options.scaleContent != null) doScaleContent = options.scaleContent;
		if (options.from != null) 
		{
			if (Spry.Effect.Utils.isPercentValue(options.from))
			{
				fromRect.width = originalWidth * (Spry.Effect.Utils.getPercentValue(options.from) / 100);
				fromRect.height = originalHeight * (Spry.Effect.Utils.getPercentValue(options.from) / 100);
			}
			else
			{
				if(calcHeight)
				{
					fromRect.height = pixelValue(options.from);
					fromRect.width  = pixelValue(options.from) / propFactor;
				}
				else
				{
					fromRect.width = pixelValue(options.from);
					fromRect.height = propFactor * pixelValue(options.from);
				}
			}
		}
		if (options.to != null)
		{
			if (Spry.Effect.Utils.isPercentValue(options.to))
			{
				toRect.width = originalWidth * (Spry.Effect.Utils.getPercentValue(options.to) / 100);
				toRect.height = originalHeight * (Spry.Effect.Utils.getPercentValue(options.to) / 100);
			}
			else
			{
				if(calcHeight)
				{
					toRect.height = pixelValue(options.to);
					toRect.width  = pixelValue(options.to) / propFactor;
				}
				else
				{
					toRect.width = pixelValue(options.to);
					toRect.height = propFactor * pixelValue(options.to);
				}
			}
		}
		if (options.toggle != null) doToggle = options.toggle;
		if (options.transition != null) kindOfTransition = options.transition;
		if (options.fps != null) fps = options.fps;
	}

	options = {duration:durationInMilliseconds, toggle:doToggle, transition:kindOfTransition, scaleContent:doScaleContent, useCSSBox: fullCSSBox, fps: fps};
	var sizeEffect = new Spry.Effect.Size(element, fromRect, toRect, options);
	this.addParallelEffect(sizeEffect);

	if(growFromCenter)
	{
		Spry.Effect.makePositioned(element);

		var startOffsetPosition = new Spry.Effect.Utils.Position();
		startOffsetPosition.x = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(element, "left"), 10);
		startOffsetPosition.y = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(element, "top"), 10);	
		if (!startOffsetPosition.x) startOffsetPosition.x = 0;
		if (!startOffsetPosition.y) startOffsetPosition.y = 0;

		options = {duration:durationInMilliseconds, toggle:doToggle, transition:kindOfTransition, from: optionFrom, to: optionTo, fps: fps};
		var fromPos = new Spry.Effect.Utils.Position;
		fromPos.x = startOffsetPosition.x + (originalWidth - fromRect.width) / 2.0;
		fromPos.y = startOffsetPosition.y + (originalHeight - fromRect.height) / 2.0;

		var toPos = new Spry.Effect.Utils.Position;
		toPos.x = startOffsetPosition.x + (originalWidth - toRect.width) / 2.0;
		toPos.y = startOffsetPosition.y + (originalHeight - toRect.height) / 2.0;

		var moveEffect = new Spry.Effect.Move(element, fromPos, toPos, options);
		this.addParallelEffect(moveEffect);
	}
};

Spry.Effect.Grow.prototype = new Spry.Effect.Cluster();
Spry.Effect.Grow.prototype.constructor = Spry.Effect.Grow;

Spry.Effect.Shake = function (element, options) 
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Shake'); 

	Spry.Effect.Cluster.call(this, options);

	// toggle is not supported
	this.options.direction = false;
	if (this.options.toggle)
		this.options.toggle = false;

	this.name = 'Shake';

	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;
	var durationInMilliseconds = 100;
	var kindOfTransition = Spry.linearTransition;
	var fps = 60;
	var steps = 4;

	if (options)
	{
		if (options.duration != null) steps = Math.ceil(this.options.duration / durationInMilliseconds) - 1;
		if (options.fps != null) fps = options.fps;
		if (options.transition != null) kindOfTransition = options.transition;
	}

	Spry.Effect.makePositioned(element);
	
	var startOffsetPosition = new Spry.Effect.Utils.Position();
	startOffsetPosition.x = parseInt(Spry.Effect.getStyleProp(element, "left"), 10);
	startOffsetPosition.y = parseInt(Spry.Effect.getStyleProp(element, "top"), 10);
	if (!startOffsetPosition.x) startOffsetPosition.x = 0;
	if (!startOffsetPosition.y) startOffsetPosition.y = 0;

	var centerPos = new Spry.Effect.Utils.Position;
	centerPos.x = startOffsetPosition.x;
	centerPos.y = startOffsetPosition.y;

	var rightPos = new Spry.Effect.Utils.Position;
	rightPos.x = startOffsetPosition.x + 20;
	rightPos.y = startOffsetPosition.y + 0;

	var leftPos = new Spry.Effect.Utils.Position;
	leftPos.x = startOffsetPosition.x + -20;
	leftPos.y = startOffsetPosition.y + 0;

	options = {duration:Math.ceil(durationInMilliseconds / 2), toggle:false, fps: fps, transition: kindOfTransition};
	var effect = new Spry.Effect.Move(element, centerPos, rightPos, options);
	this.addNextEffect(effect);

	options = {duration:durationInMilliseconds, toggle:false, fps:fps, transition: kindOfTransition};
	var effectToRight = new Spry.Effect.Move(element, rightPos, leftPos, options);
	var effectToLeft = new Spry.Effect.Move(element, leftPos, rightPos, options);

	for (var i=0; i < steps; i++)
	{
		if (i % 2 == 0)
			this.addNextEffect(effectToRight);
		else
			this.addNextEffect(effectToLeft);
	}
	var pos = (steps % 2 == 0) ? rightPos: leftPos;

	options = {duration:Math.ceil(durationInMilliseconds / 2), toggle:false, fps: fps, transition: kindOfTransition};
	var effect = new Spry.Effect.Move(element, pos, centerPos, options);
	this.addNextEffect(effect);
};
Spry.Effect.Shake.prototype = new Spry.Effect.Cluster();
Spry.Effect.Shake.prototype.constructor = Spry.Effect.Shake;
Spry.Effect.Shake.prototype.doToggle = function(){};

Spry.Effect.Squish = function (element, options) 
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Squish');

	if (!options)
		options = {};
	if (!options.to)
		options.to = '0%';
	if (!options.from)
		options.from = '100%';

	options.growCenter = false;
	Spry.Effect.Grow.call(this, element, options);
	this.name = 'Squish';
};
Spry.Effect.Squish.prototype = new Spry.Effect.Grow();
Spry.Effect.Squish.prototype.constructor = Spry.Effect.Squish;

Spry.Effect.Pulsate = function (element, options) 
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Pulsate');

	Spry.Effect.Cluster.call(this, options);

	// toggle is not supported
	this.options.direction = false;
	if (this.options.toggle)
		this.options.toggle = false;

	var element = Spry.Effect.getElement(element);
	var originalOpacity = 0;
	this.element = element;
	if (!this.element)
		return;

	this.name = 'Pulsate';
	var durationInMilliseconds = 100;
	var fromOpacity = 100.0;
	var toOpacity = 0.0;
	var doToggle = false;
	var kindOfTransition = Spry.linearTransition;
	var fps = 60;
	if(/MSIE/.test(navigator.userAgent))
		originalOpacity = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(this.element, 'filter').replace(/alpha\(opacity=([0-9]{1,3})\)/g, '$1'), 10);
	else
		originalOpacity = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(this.element, 'opacity') * 100, 10);

	if (isNaN(originalOpacity)){
		originalOpacity = 100;
	}

	if (options)
	{
		if (options.from != null){
			if (Spry.Effect.Utils.isPercentValue(options.from))
				fromOpacity = Spry.Effect.Utils.getPercentValue(options.from) * originalOpacity / 100;
			else
				fromOpacity = options.from;
		}
		if (options.to != null)
		{	
			if (Spry.Effect.Utils.isPercentValue(options.to))
				toOpacity = Spry.Effect.Utils.getPercentValue(options.to) * originalOpacity / 100;
			else
				toOpacity = options.to;
		}
		if (options.transition != null) kindOfTransition = options.transition;
		if (options.fps != null) fps = options.fps;
	}

	options = {duration:durationInMilliseconds, toggle:doToggle, transition:kindOfTransition, fps:fps};
	fromOpacity = fromOpacity / 100.0;
	toOpacity = toOpacity / 100.0;

	var fadeEffect = new Spry.Effect.Opacity(element, fromOpacity, toOpacity, options);
	var appearEffect = new Spry.Effect.Opacity(element, toOpacity, fromOpacity, options);
	var steps = parseInt(this.options.duration / 200, 10);
	for (var i=0; i < steps; i++){ 
		this.addNextEffect(fadeEffect);
		this.addNextEffect(appearEffect);
	}
};
Spry.Effect.Pulsate.prototype = new Spry.Effect.Cluster();
Spry.Effect.Pulsate.prototype.constructor = Spry.Effect.Pulsate;
Spry.Effect.Pulsate.prototype.doToggle = function(){};

Spry.Effect.Puff = function (element, options)
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Puff'); 

	Spry.Effect.Cluster.call(this, options);

	var element = Spry.Effect.getElement(element);
	this.element = element;	
	if (!this.element)
		return;
	this.name = 'Puff';
	var doToggle = false;
	var doScaleContent = false;
	var durationInMilliseconds = 1000;
	var kindOfTransition = Spry.fifthTransition;
	var fps = 60;

	Spry.Effect.makePositioned(element); // for move

	if (options){
		if (options.toggle != null) doToggle = options.toggle;
		if (options.duration != null) durationInMilliseconds = options.duration;
		if (options.transition != null) kindOfTransition = options.transition;
		if (options.fps != null) fps = options.fps;
	}
	var originalRect = Spry.Effect.getDimensions(element);
	var startWidth = originalRect.width;
	var startHeight = originalRect.height;

	options = {duration:durationInMilliseconds, toggle:doToggle, transition: kindOfTransition, fps: fps};

	var fromOpacity = 1.0;
	var toOpacity = 0.0;
	var opacityEffect = new Spry.Effect.Opacity(element, fromOpacity, toOpacity, options);
	this.addParallelEffect(opacityEffect);

	var fromPos = Spry.Effect.getPosition(element);

	var toPos = new Spry.Effect.Utils.Position;
	toPos.x = startWidth / 2.0 * -1.0;
	toPos.y = startHeight / 2.0 * -1.0;

	options = {duration:durationInMilliseconds, toggle:doToggle, transition:kindOfTransition, from: fromPos, to: toPos, fps: fps};
	var moveEffect = new Spry.Effect.Move(element, fromPos, toPos, options);
	this.addParallelEffect(moveEffect);

	var self = this;
	this.addObserver({
		onPreEffect:function(){if (self.direction == Spry.backwards){self.element.style.display = 'block';}},
		onPostEffect: function(){if (self.direction == Spry.forwards){self.element.style.display = 'none';}}
	});
};
Spry.Effect.Puff.prototype = new Spry.Effect.Cluster;
Spry.Effect.Puff.prototype.constructor = Spry.Effect.Puff;

Spry.Effect.DropOut = function (element, options)
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('DropOut');

	Spry.Effect.Cluster.call(this, options);

	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;
	var durationInMilliseconds = 1000;
	var fps = 60;
	var kindOfTransition = Spry.fifthTransition;
	var direction = Spry.forwards;
	var doToggle = false;
	this.name = 'DropOut';

	Spry.Effect.makePositioned(element);

	if (options)
	{
		if (options.duration != null) durationInMilliseconds = options.duration;
		if (options.toggle != null) doToggle = options.toggle;
		if (options.fps != null) fps = options.fps;
		if (options.transition != null) kindOfTransition = options.transition;
		if (options.dropIn != null) direction = -1;
	}

	var startOffsetPosition = new Spry.Effect.Utils.Position();
	startOffsetPosition.x = parseInt(Spry.Effect.getStyleProp(element, "left"), 10);
	startOffsetPosition.y = parseInt(Spry.Effect.getStyleProp(element, "top"), 10);	
	if (!startOffsetPosition.x) startOffsetPosition.x = 0;
	if (!startOffsetPosition.y) startOffsetPosition.y = 0;

	var fromPos = new Spry.Effect.Utils.Position;
	fromPos.x = startOffsetPosition.x + 0;
	fromPos.y = startOffsetPosition.y + 0;

	var toPos = new Spry.Effect.Utils.Position;
	toPos.x = startOffsetPosition.x + 0;
	toPos.y = startOffsetPosition.y + (direction * 160);

	options = {from:fromPos, to:toPos, duration:durationInMilliseconds, toggle:doToggle, transition: kindOfTransition, fps: fps};
	var moveEffect = new Spry.Effect.Move(element, options.from, options.to, options);
	this.addParallelEffect(moveEffect);

	var fromOpacity = 1.0;
	var toOpacity = 0.0;
	options = {duration:durationInMilliseconds, toggle:doToggle, transition: kindOfTransition, fps: fps};
	var opacityEffect = new Spry.Effect.Opacity(element, fromOpacity, toOpacity, options);
	this.addParallelEffect(opacityEffect);

	var self = this;
	this.addObserver({
		onPreEffect:function(){self.element.style.display = 'block';},
		onPostEffect: function(){if (self.direction == Spry.forwards){self.element.style.display = 'none';}}
	});

};
Spry.Effect.DropOut.prototype = new Spry.Effect.Cluster();
Spry.Effect.DropOut.prototype.constructor = Spry.Effect.DropOut;

Spry.Effect.Fold = function (element, options)
{
	if (!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Fold');

	Spry.Effect.Cluster.call(this, options);

	var element = Spry.Effect.getElement(element);
	this.element = element;
	if (!this.element)
		return;
	this.name = 'Fold';
	var durationInMilliseconds = 1000;
	var doToggle = false;
	var doScaleContent = true;
	var fullCSSBox = false;
	var kindOfTransition = Spry.fifthTransition;
	var fps = fps;
	
	Spry.Effect.makeClipping(element);

	var originalRect = Spry.Effect.getDimensionsRegardlessOfDisplayState(element);
	var startWidth = originalRect.width;
	var startHeight = originalRect.height;

	var stopWidth = startWidth;
	var stopHeight = startHeight / 5;

	var fromRect = new Spry.Effect.Utils.Rectangle;
	fromRect.width = startWidth;
	fromRect.height = startHeight;

	var toRect = new Spry.Effect.Utils.Rectangle;
	toRect.width = stopWidth;
	toRect.height = stopHeight;

	if (options)
	{
		if (options.duration != null) durationInMilliseconds = Math.ceil(options.duration/2);
		if (options.toggle != null) doToggle = options.toggle;
		if (options.useCSSBox != null) fullCSSBox = options.useCSSBox; 
		if (options.fps != null) fps = options.fps;
		if (options.transition != null) kindOfTransition = options.transition;
	}

	options = {duration:durationInMilliseconds, toggle:doToggle, scaleContent:doScaleContent, useCSSBox: fullCSSBox, transition: kindOfTransition, fps: fps};
	var sizeEffect = new Spry.Effect.Size(element, fromRect, toRect, options);
	this.addNextEffect(sizeEffect);

	fromRect.width = toRect.width;
	fromRect.height = toRect.height;
	toRect.width = '0%';
	var sizeEffect = new Spry.Effect.Size(element, fromRect, toRect, options);
	this.addNextEffect(sizeEffect);
};

Spry.Effect.Fold.prototype = new Spry.Effect.Cluster();
Spry.Effect.Fold.prototype.constructor = Spry.Effect.Fold;

//////////////////////////////////////////////////////////////
// 																													//
// The names of some of the static effect functions 		 		//
// changed in Spry 1.5. These wrappers will insure that we 	//
// remain compatible with previous versions of Spry.				//
// 																													//
//////////////////////////////////////////////////////////////

Spry.Effect.DoFade = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Fade', element, options);
};

Spry.Effect.DoBlind = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Blind', element, options);
};

Spry.Effect.DoHighlight = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Highlight', element, options);
};

Spry.Effect.DoSlide = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Slide', element, options);
};

Spry.Effect.DoGrow = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Grow', element, options);
};

Spry.Effect.DoShake = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Shake', element, options);
};

Spry.Effect.DoSquish = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Squish', element, options);
};

Spry.Effect.DoPulsate = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Pulsate', element, options);
};

Spry.Effect.DoPuff = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Puff', element, options);
};

Spry.Effect.DoDropOut = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('DropOut', element, options);
};

Spry.Effect.DoFold = function (element, options)
{
	return Spry.Effect.Utils.DoEffect('Fold', element, options);
};









/*!
 * jQuery Cycle Plugin (with Transition Definitions)
 * Examples and documentation at: http://jquery.malsup.com/cycle/
 * Copyright (c) 2007-2010 M. Alsup
 * Version: 2.88 (08-JUN-2010)
 * Dual licensed under the MIT and GPL licenses.
 * http://jquery.malsup.com/license.html
 * Requires: jQuery v1.2.6 or later
 */
;(function($) {

var ver = '2.88';

// if $.support is not defined (pre jQuery 1.3) add what I need
if ($.support == undefined) {
    $.support = {
        opacity: !($.browser.msie)
    };
} 

function debug(s) {
    if ($.fn.cycle.debug)
        log(s);
}       
function log() {
    if (window.console && window.console.log)
        window.console.log('[cycle] ' + Array.prototype.join.call(arguments,' '));
};

// the options arg can be...
//   a number  - indicates an immediate transition should occur to the given slide index
//   a string  - 'pause', 'resume', 'toggle', 'next', 'prev', 'stop', 'destroy' or the name of a transition effect (ie, 'fade', 'zoom', etc)
//   an object - properties to control the slideshow
//
// the arg2 arg can be...
//   the name of an fx (only used in conjunction with a numeric value for 'options')
//   the value true (only used in first arg == 'resume') and indicates
//   that the resume should occur immediately (not wait for next timeout)

$.fn.cycle = function(options, arg2) {
    var o = { s: this.selector, c: this.context };

    // in 1.3+ we can fix mistakes with the ready state
    if (this.length === 0 && options != 'stop') {
        if (!$.isReady && o.s) {
            log('DOM not ready, queuing slideshow');
            $(function() {
                $(o.s,o.c).cycle(options,arg2);
            });
            return this;
        }
        // is your DOM ready?  http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
        log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
        return this;
    }

    // iterate the matched nodeset
    return this.each(function() {
        var opts = handleArguments(this, options, arg2);
        if (opts === false)
            return;

        opts.updateActivePagerLink = opts.updateActivePagerLink || $.fn.cycle.updateActivePagerLink;
        
        // stop existing slideshow for this container (if there is one)
        if (this.cycleTimeout)
            clearTimeout(this.cycleTimeout);
        this.cycleTimeout = this.cyclePause = 0;

        var $cont = $(this);
        var $slides = opts.slideExpr ? $(opts.slideExpr, this) : $cont.children();
        var els = $slides.get();
        if (els.length < 2) {
            log('terminating; too few slides: ' + els.length);
            return;
        }

        var opts2 = buildOptions($cont, $slides, els, opts, o);
        if (opts2 === false)
            return;

        var startTime = opts2.continuous ? 10 : getTimeout(els[opts2.currSlide], els[opts2.nextSlide], opts2, !opts2.rev);

        // if it's an auto slideshow, kick it off
        if (startTime) {
            startTime += (opts2.delay || 0);
            if (startTime < 10)
                startTime = 10;
            debug('first timeout: ' + startTime);
            this.cycleTimeout = setTimeout(function(){go(els,opts2,0,(!opts2.rev && !opts.backwards))}, startTime);
        }
    });
};

// process the args that were passed to the plugin fn
function handleArguments(cont, options, arg2) {
    if (cont.cycleStop == undefined)
        cont.cycleStop = 0;
    if (options === undefined || options === null)
        options = {};
    if (options.constructor == String) {
        switch(options) {
        case 'destroy':
        case 'stop':
            var opts = $(cont).data('cycle.opts');
            if (!opts)
                return false;
            cont.cycleStop++; // callbacks look for change
            if (cont.cycleTimeout)
                clearTimeout(cont.cycleTimeout);
            cont.cycleTimeout = 0;
            $(cont).removeData('cycle.opts');
            if (options == 'destroy')
                destroy(opts);
            return false;
        case 'toggle':
            cont.cyclePause = (cont.cyclePause === 1) ? 0 : 1;
            checkInstantResume(cont.cyclePause, arg2, cont);
            return false;
        case 'pause':
            cont.cyclePause = 1;
            return false;
        case 'resume':
            cont.cyclePause = 0;
            checkInstantResume(false, arg2, cont);
            return false;
        case 'prev':
        case 'next':
            var opts = $(cont).data('cycle.opts');
            if (!opts) {
                log('options not found, "prev/next" ignored');
                return false;
            }
            $.fn.cycle[options](opts);
            return false;
        default:
            options = { fx: options };
        };
        return options;
    }
    else if (options.constructor == Number) {
        // go to the requested slide
        var num = options;
        options = $(cont).data('cycle.opts');
        if (!options) {
            log('options not found, can not advance slide');
            return false;
        }
        if (num < 0 || num >= options.elements.length) {
            log('invalid slide index: ' + num);
            return false;
        }
        options.nextSlide = num;
        if (cont.cycleTimeout) {
            clearTimeout(cont.cycleTimeout);
            cont.cycleTimeout = 0;
        }
        if (typeof arg2 == 'string')
            options.oneTimeFx = arg2;
        go(options.elements, options, 1, num >= options.currSlide);
        return false;
    }
    return options;
    
    function checkInstantResume(isPaused, arg2, cont) {
        if (!isPaused && arg2 === true) { // resume now!
            var options = $(cont).data('cycle.opts');
            if (!options) {
                log('options not found, can not resume');
                return false;
            }
            if (cont.cycleTimeout) {
                clearTimeout(cont.cycleTimeout);
                cont.cycleTimeout = 0;
            }
            go(options.elements, options, 1, (!opts.rev && !opts.backwards));
        }
    }
};

function removeFilter(el, opts) {
    if (!$.support.opacity && opts.cleartype && el.style.filter) {
        try { el.style.removeAttribute('filter'); }
        catch(smother) {} // handle old opera versions
    }
};

// unbind event handlers
function destroy(opts) {
    if (opts.next)
        $(opts.next).unbind(opts.prevNextEvent);
    if (opts.prev)
        $(opts.prev).unbind(opts.prevNextEvent);
    
    if (opts.pager || opts.pagerAnchorBuilder)
        $.each(opts.pagerAnchors || [], function() {
            this.unbind().remove();
        });
    opts.pagerAnchors = null;
    if (opts.destroy) // callback
        opts.destroy(opts);
};

// one-time initialization
function buildOptions($cont, $slides, els, options, o) {
    // support metadata plugin (v1.0 and v2.0)
    var opts = $.extend({}, $.fn.cycle.defaults, options || {}, $.metadata ? $cont.metadata() : $.meta ? $cont.data() : {});
    if (opts.autostop)
        opts.countdown = opts.autostopCount || els.length;

    var cont = $cont[0];
    $cont.data('cycle.opts', opts);
    opts.$cont = $cont;
    opts.stopCount = cont.cycleStop;
    opts.elements = els;
    opts.before = opts.before ? [opts.before] : [];
    opts.after = opts.after ? [opts.after] : [];
    opts.after.unshift(function(){ opts.busy=0; });

    // push some after callbacks
    if (!$.support.opacity && opts.cleartype)
        opts.after.push(function() { removeFilter(this, opts); });
    if (opts.continuous)
        opts.after.push(function() { go(els,opts,0,(!opts.rev && !opts.backwards)); });

    saveOriginalOpts(opts);

    // clearType corrections
    if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg)
        clearTypeFix($slides);

    // container requires non-static position so that slides can be position within
    if ($cont.css('position') == 'static')
        $cont.css('position', 'relative');
    if (opts.width)
        $cont.width(opts.width);
    if (opts.height && opts.height != 'auto')
        $cont.height(opts.height);

    if (opts.startingSlide)
        opts.startingSlide = parseInt(opts.startingSlide);
    else if (opts.backwards)
        opts.startingSlide = els.length - 1;

    // if random, mix up the slide array
    if (opts.random) {
        opts.randomMap = [];
        for (var i = 0; i < els.length; i++)
            opts.randomMap.push(i);
        opts.randomMap.sort(function(a,b) {return Math.random() - 0.5;});
        opts.randomIndex = 1;
        opts.startingSlide = opts.randomMap[1];
    }
    else if (opts.startingSlide >= els.length)
        opts.startingSlide = 0; // catch bogus input
    opts.currSlide = opts.startingSlide || 0;
    var first = opts.startingSlide;

    // set position and zIndex on all the slides
    $slides.css({position: 'absolute', top:0, left:0}).hide().each(function(i) {
        var z;
        if (opts.backwards)
            z = first ? i <= first ? els.length + (i-first) : first-i : els.length-i;
        else
            z = first ? i >= first ? els.length - (i-first) : first-i : els.length-i;
        $(this).css('z-index', z)
    });

    // make sure first slide is visible
    $(els[first]).css('opacity',1).show(); // opacity bit needed to handle restart use case
    removeFilter(els[first], opts);

    // stretch slides
    if (opts.fit && opts.width)
        $slides.width(opts.width);
    if (opts.fit && opts.height && opts.height != 'auto')
        $slides.height(opts.height);

    // stretch container
    var reshape = opts.containerResize && !$cont.innerHeight();
    if (reshape) { // do this only if container has no size http://tinyurl.com/da2oa9
        var maxw = 0, maxh = 0;
        for(var j=0; j < els.length; j++) {
            var $e = $(els[j]), e = $e[0], w = $e.outerWidth(), h = $e.outerHeight();
            if (!w) w = e.offsetWidth || e.width || $e.attr('width')
            if (!h) h = e.offsetHeight || e.height || $e.attr('height');
            maxw = w > maxw ? w : maxw;
            maxh = h > maxh ? h : maxh;
        }
        if (maxw > 0 && maxh > 0)
            $cont.css({width:maxw+'px',height:maxh+'px'});
    }

    if (opts.pause)
        $cont.hover(function(){this.cyclePause++;},function(){this.cyclePause--;});

    if (supportMultiTransitions(opts) === false)
        return false;

    // apparently a lot of people use image slideshows without height/width attributes on the images.
    // Cycle 2.50+ requires the sizing info for every slide; this block tries to deal with that.
    var requeue = false;
    options.requeueAttempts = options.requeueAttempts || 0;
    $slides.each(function() {
        // try to get height/width of each slide
        var $el = $(this);
        this.cycleH = (opts.fit && opts.height) ? opts.height : ($el.height() || this.offsetHeight || this.height || $el.attr('height') || 0);
        this.cycleW = (opts.fit && opts.width) ? opts.width : ($el.width() || this.offsetWidth || this.width || $el.attr('width') || 0);

        if ( $el.is('img') ) {
            // sigh..  sniffing, hacking, shrugging...  this crappy hack tries to account for what browsers do when
            // an image is being downloaded and the markup did not include sizing info (height/width attributes);
            // there seems to be some "default" sizes used in this situation
            var loadingIE   = ($.browser.msie  && this.cycleW == 28 && this.cycleH == 30 && !this.complete);
            var loadingFF   = ($.browser.mozilla && this.cycleW == 34 && this.cycleH == 19 && !this.complete);
            var loadingOp   = ($.browser.opera && ((this.cycleW == 42 && this.cycleH == 19) || (this.cycleW == 37 && this.cycleH == 17)) && !this.complete);
            var loadingOther = (this.cycleH == 0 && this.cycleW == 0 && !this.complete);
            // don't requeue for images that are still loading but have a valid size
            if (loadingIE || loadingFF || loadingOp || loadingOther) {
                if (o.s && opts.requeueOnImageNotLoaded && ++options.requeueAttempts < 100) { // track retry count so we don't loop forever
                    log(options.requeueAttempts,' - img slide not loaded, requeuing slideshow: ', this.src, this.cycleW, this.cycleH);
                    setTimeout(function() {$(o.s,o.c).cycle(options)}, opts.requeueTimeout);
                    requeue = true;
                    return false; // break each loop
                }
                else {
                    log('could not determine size of image: '+this.src, this.cycleW, this.cycleH);
                }
            }
        }
        return true;
    });

    if (requeue)
        return false;

    opts.cssBefore = opts.cssBefore || {};
    opts.animIn = opts.animIn || {};
    opts.animOut = opts.animOut || {};

    $slides.not(':eq('+first+')').css(opts.cssBefore);
    if (opts.cssFirst)
        $($slides[first]).css(opts.cssFirst);

    if (opts.timeout) {
        opts.timeout = parseInt(opts.timeout);
        // ensure that timeout and speed settings are sane
        if (opts.speed.constructor == String)
            opts.speed = $.fx.speeds[opts.speed] || parseInt(opts.speed);
        if (!opts.sync)
            opts.speed = opts.speed / 2;
        
        var buffer = opts.fx == 'shuffle' ? 500 : 250;
        while((opts.timeout - opts.speed) < buffer) // sanitize timeout
            opts.timeout += opts.speed;
    }
    if (opts.easing)
        opts.easeIn = opts.easeOut = opts.easing;
    if (!opts.speedIn)
        opts.speedIn = opts.speed;
    if (!opts.speedOut)
        opts.speedOut = opts.speed;

    opts.slideCount = els.length;
    opts.currSlide = opts.lastSlide = first;
    if (opts.random) {
        if (++opts.randomIndex == els.length)
            opts.randomIndex = 0;
        opts.nextSlide = opts.randomMap[opts.randomIndex];
    }
    else if (opts.backwards)
        opts.nextSlide = opts.startingSlide == 0 ? (els.length-1) : opts.startingSlide-1;
    else
        opts.nextSlide = opts.startingSlide >= (els.length-1) ? 0 : opts.startingSlide+1;

    // run transition init fn
    if (!opts.multiFx) {
        var init = $.fn.cycle.transitions[opts.fx];
        if ($.isFunction(init))
            init($cont, $slides, opts);
        else if (opts.fx != 'custom' && !opts.multiFx) {
            log('unknown transition: ' + opts.fx,'; slideshow terminating');
            return false;
        }
    }

    // fire artificial events
    var e0 = $slides[first];
    if (opts.before.length)
        opts.before[0].apply(e0, [e0, e0, opts, true]);
    if (opts.after.length > 1)
        opts.after[1].apply(e0, [e0, e0, opts, true]);

    if (opts.next)
        $(opts.next).bind(opts.prevNextEvent,function(){return advance(opts,opts.rev?-1:1)});
    if (opts.prev)
        $(opts.prev).bind(opts.prevNextEvent,function(){return advance(opts,opts.rev?1:-1)});
    if (opts.pager || opts.pagerAnchorBuilder)
        buildPager(els,opts);

    exposeAddSlide(opts, els);

    return opts;
};

// save off original opts so we can restore after clearing state
function saveOriginalOpts(opts) {
    opts.original = { before: [], after: [] };
    opts.original.cssBefore = $.extend({}, opts.cssBefore);
    opts.original.cssAfter  = $.extend({}, opts.cssAfter);
    opts.original.animIn    = $.extend({}, opts.animIn);
    opts.original.animOut   = $.extend({}, opts.animOut);
    $.each(opts.before, function() { opts.original.before.push(this); });
    $.each(opts.after,  function() { opts.original.after.push(this); });
};

function supportMultiTransitions(opts) {
    var i, tx, txs = $.fn.cycle.transitions;
    // look for multiple effects
    if (opts.fx.indexOf(',') > 0) {
        opts.multiFx = true;
        opts.fxs = opts.fx.replace(/\s*/g,'').split(',');
        // discard any bogus effect names
        for (i=0; i < opts.fxs.length; i++) {
            var fx = opts.fxs[i];
            tx = txs[fx];
            if (!tx || !txs.hasOwnProperty(fx) || !$.isFunction(tx)) {
                log('discarding unknown transition: ',fx);
                opts.fxs.splice(i,1);
                i--;
            }
        }
        // if we have an empty list then we threw everything away!
        if (!opts.fxs.length) {
            log('No valid transitions named; slideshow terminating.');
            return false;
        }
    }
    else if (opts.fx == 'all') {  // auto-gen the list of transitions
        opts.multiFx = true;
        opts.fxs = [];
        for (p in txs) {
            tx = txs[p];
            if (txs.hasOwnProperty(p) && $.isFunction(tx))
                opts.fxs.push(p);
        }
    }
    if (opts.multiFx && opts.randomizeEffects) {
        // munge the fxs array to make effect selection random
        var r1 = Math.floor(Math.random() * 20) + 30;
        for (i = 0; i < r1; i++) {
            var r2 = Math.floor(Math.random() * opts.fxs.length);
            opts.fxs.push(opts.fxs.splice(r2,1)[0]);
        }
        debug('randomized fx sequence: ',opts.fxs);
    }
    return true;
};

// provide a mechanism for adding slides after the slideshow has started
function exposeAddSlide(opts, els) {
    opts.addSlide = function(newSlide, prepend) {
        var $s = $(newSlide), s = $s[0];
        if (!opts.autostopCount)
            opts.countdown++;
        els[prepend?'unshift':'push'](s);
        if (opts.els)
            opts.els[prepend?'unshift':'push'](s); // shuffle needs this
        opts.slideCount = els.length;

        $s.css('position','absolute');
        $s[prepend?'prependTo':'appendTo'](opts.$cont);

        if (prepend) {
            opts.currSlide++;
            opts.nextSlide++;
        }

        if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg)
            clearTypeFix($s);

        if (opts.fit && opts.width)
            $s.width(opts.width);
        if (opts.fit && opts.height && opts.height != 'auto')
            $slides.height(opts.height);
        s.cycleH = (opts.fit && opts.height) ? opts.height : $s.height();
        s.cycleW = (opts.fit && opts.width) ? opts.width : $s.width();

        $s.css(opts.cssBefore);

        if (opts.pager || opts.pagerAnchorBuilder)
            $.fn.cycle.createPagerAnchor(els.length-1, s, $(opts.pager), els, opts);

        if ($.isFunction(opts.onAddSlide))
            opts.onAddSlide($s);
        else
            $s.hide(); // default behavior
    };
}

// reset internal state; we do this on every pass in order to support multiple effects
$.fn.cycle.resetState = function(opts, fx) {
    fx = fx || opts.fx;
    opts.before = []; opts.after = [];
    opts.cssBefore = $.extend({}, opts.original.cssBefore);
    opts.cssAfter  = $.extend({}, opts.original.cssAfter);
    opts.animIn = $.extend({}, opts.original.animIn);
    opts.animOut   = $.extend({}, opts.original.animOut);
    opts.fxFn = null;
    $.each(opts.original.before, function() { opts.before.push(this); });
    $.each(opts.original.after,  function() { opts.after.push(this); });

    // re-init
    var init = $.fn.cycle.transitions[fx];
    if ($.isFunction(init))
        init(opts.$cont, $(opts.elements), opts);
};

// this is the main engine fn, it handles the timeouts, callbacks and slide index mgmt
function go(els, opts, manual, fwd) {
    // opts.busy is true if we're in the middle of an animation
    if (manual && opts.busy && opts.manualTrump) {
        // let manual transitions requests trump active ones
        debug('manualTrump in go(), stopping active transition');
        $(els).stop(true,true);
        opts.busy = false;
    }
    // don't begin another timeout-based transition if there is one active
    if (opts.busy) {
        debug('transition active, ignoring new tx request');
        return;
    }

    var p = opts.$cont[0], curr = els[opts.currSlide], next = els[opts.nextSlide];

    // stop cycling if we have an outstanding stop request
    if (p.cycleStop != opts.stopCount || p.cycleTimeout === 0 && !manual)
        return;

    // check to see if we should stop cycling based on autostop options
    if (!manual && !p.cyclePause && !opts.bounce &&
        ((opts.autostop && (--opts.countdown <= 0)) ||
        (opts.nowrap && !opts.random && opts.nextSlide < opts.currSlide))) {
        if (opts.end)
            opts.end(opts);
        return;
    }

    // if slideshow is paused, only transition on a manual trigger
    var changed = false;
    if ((manual || !p.cyclePause) && (opts.nextSlide != opts.currSlide)) {
        changed = true;
        var fx = opts.fx;
        // keep trying to get the slide size if we don't have it yet
        curr.cycleH = curr.cycleH || $(curr).height();
        curr.cycleW = curr.cycleW || $(curr).width();
        next.cycleH = next.cycleH || $(next).height();
        next.cycleW = next.cycleW || $(next).width();

        // support multiple transition types
        if (opts.multiFx) {
            if (opts.lastFx == undefined || ++opts.lastFx >= opts.fxs.length)
                opts.lastFx = 0;
            fx = opts.fxs[opts.lastFx];
            opts.currFx = fx;
        }

        // one-time fx overrides apply to:  $('div').cycle(3,'zoom');
        if (opts.oneTimeFx) {
            fx = opts.oneTimeFx;
            opts.oneTimeFx = null;
        }

        $.fn.cycle.resetState(opts, fx);

        // run the before callbacks
        if (opts.before.length)
            $.each(opts.before, function(i,o) {
                if (p.cycleStop != opts.stopCount) return;
                o.apply(next, [curr, next, opts, fwd]);
            });

        // stage the after callacks
        var after = function() {
            $.each(opts.after, function(i,o) {
                if (p.cycleStop != opts.stopCount) return;
                o.apply(next, [curr, next, opts, fwd]);
            });
        };

        debug('tx firing; currSlide: ' + opts.currSlide + '; nextSlide: ' + opts.nextSlide);
        
        // get ready to perform the transition
        opts.busy = 1;
        if (opts.fxFn) // fx function provided?
            opts.fxFn(curr, next, opts, after, fwd, manual && opts.fastOnEvent);
        else if ($.isFunction($.fn.cycle[opts.fx])) // fx plugin ?
            $.fn.cycle[opts.fx](curr, next, opts, after, fwd, manual && opts.fastOnEvent);
        else
            $.fn.cycle.custom(curr, next, opts, after, fwd, manual && opts.fastOnEvent);
    }

    if (changed || opts.nextSlide == opts.currSlide) {
        // calculate the next slide
        opts.lastSlide = opts.currSlide;
        if (opts.random) {
            opts.currSlide = opts.nextSlide;
            if (++opts.randomIndex == els.length)
                opts.randomIndex = 0;
            opts.nextSlide = opts.randomMap[opts.randomIndex];
            if (opts.nextSlide == opts.currSlide)
                opts.nextSlide = (opts.currSlide == opts.slideCount - 1) ? 0 : opts.currSlide + 1;
        }
        else if (opts.backwards) {
            var roll = (opts.nextSlide - 1) < 0;
            if (roll && opts.bounce) {
                opts.backwards = !opts.backwards;
                opts.nextSlide = 1;
                opts.currSlide = 0;
            }
            else {
                opts.nextSlide = roll ? (els.length-1) : opts.nextSlide-1;
                opts.currSlide = roll ? 0 : opts.nextSlide+1;
            }
        }
        else { // sequence
            var roll = (opts.nextSlide + 1) == els.length;
            if (roll && opts.bounce) {
                opts.backwards = !opts.backwards;
                opts.nextSlide = els.length-2;
                opts.currSlide = els.length-1;
            }
            else {
                opts.nextSlide = roll ? 0 : opts.nextSlide+1;
                opts.currSlide = roll ? els.length-1 : opts.nextSlide-1;
            }
        }
    }
    if (changed && opts.pager)
        opts.updateActivePagerLink(opts.pager, opts.currSlide, opts.activePagerClass);
    
    // stage the next transition
    var ms = 0;
    if (opts.timeout && !opts.continuous)
        ms = getTimeout(els[opts.currSlide], els[opts.nextSlide], opts, fwd);
    else if (opts.continuous && p.cyclePause) // continuous shows work off an after callback, not this timer logic
        ms = 10;
    if (ms > 0)
        p.cycleTimeout = setTimeout(function(){ go(els, opts, 0, (!opts.rev && !opts.backwards)) }, ms);
};

// invoked after transition
$.fn.cycle.updateActivePagerLink = function(pager, currSlide, clsName) {
   $(pager).each(function() {
       $(this).children().removeClass(clsName).eq(currSlide).addClass(clsName);
   });
};

// calculate timeout value for current transition
function getTimeout(curr, next, opts, fwd) {
    if (opts.timeoutFn) {
        // call user provided calc fn
        var t = opts.timeoutFn.call(curr,curr,next,opts,fwd);
        while ((t - opts.speed) < 250) // sanitize timeout
            t += opts.speed;
        debug('calculated timeout: ' + t + '; speed: ' + opts.speed);
        if (t !== false)
            return t;
    }
    return opts.timeout;
};

// expose next/prev function, caller must pass in state
$.fn.cycle.next = function(opts) { advance(opts, opts.rev?-1:1); };
$.fn.cycle.prev = function(opts) { advance(opts, opts.rev?1:-1);};

// advance slide forward or back
function advance(opts, val) {
    var els = opts.elements;
    var p = opts.$cont[0], timeout = p.cycleTimeout;
    if (timeout) {
        clearTimeout(timeout);
        p.cycleTimeout = 0;
    }
    if (opts.random && val < 0) {
        // move back to the previously display slide
        opts.randomIndex--;
        if (--opts.randomIndex == -2)
            opts.randomIndex = els.length-2;
        else if (opts.randomIndex == -1)
            opts.randomIndex = els.length-1;
        opts.nextSlide = opts.randomMap[opts.randomIndex];
    }
    else if (opts.random) {
        opts.nextSlide = opts.randomMap[opts.randomIndex];
    }
    else {
        opts.nextSlide = opts.currSlide + val;
        if (opts.nextSlide < 0) {
            if (opts.nowrap) return false;
            opts.nextSlide = els.length - 1;
        }
        else if (opts.nextSlide >= els.length) {
            if (opts.nowrap) return false;
            opts.nextSlide = 0;
        }
    }

    var cb = opts.onPrevNextEvent || opts.prevNextClick; // prevNextClick is deprecated
    if ($.isFunction(cb))
        cb(val > 0, opts.nextSlide, els[opts.nextSlide]);
    go(els, opts, 1, val>=0);
    return false;
};

function buildPager(els, opts) {
    var $p = $(opts.pager);
    $.each(els, function(i,o) {
        $.fn.cycle.createPagerAnchor(i,o,$p,els,opts);
    });
    opts.updateActivePagerLink(opts.pager, opts.startingSlide, opts.activePagerClass);
};

$.fn.cycle.createPagerAnchor = function(i, el, $p, els, opts) {
    var a;
    if ($.isFunction(opts.pagerAnchorBuilder)) {
        a = opts.pagerAnchorBuilder(i,el);
        debug('pagerAnchorBuilder('+i+', el) returned: ' + a);
    }
    else
        a = '<a href="#">'+(i+1)+'</a>';
        
    if (!a)
        return;
    var $a = $(a);
    // don't reparent if anchor is in the dom
    if ($a.parents('body').length === 0) {
        var arr = [];
        if ($p.length > 1) {
            $p.each(function() {
                var $clone = $a.clone(true);
                $(this).append($clone);
                arr.push($clone[0]);
            });
            $a = $(arr);
        }
        else {
            $a.appendTo($p);
        }
    }

    opts.pagerAnchors =  opts.pagerAnchors || [];
    opts.pagerAnchors.push($a);
    $a.bind(opts.pagerEvent, function(e) {
        e.preventDefault();
        opts.nextSlide = i;
        var p = opts.$cont[0], timeout = p.cycleTimeout;
        if (timeout) {
            clearTimeout(timeout);
            p.cycleTimeout = 0;
        }
        var cb = opts.onPagerEvent || opts.pagerClick; // pagerClick is deprecated
        if ($.isFunction(cb))
            cb(opts.nextSlide, els[opts.nextSlide]);
        go(els,opts,1,opts.currSlide < i); // trigger the trans
//      return false; // <== allow bubble
    });
    
    if ( ! /^click/.test(opts.pagerEvent) && !opts.allowPagerClickBubble)
        $a.bind('click.cycle', function(){return false;}); // suppress click
    
    if (opts.pauseOnPagerHover)
        $a.hover(function() { opts.$cont[0].cyclePause++; }, function() { opts.$cont[0].cyclePause--; } );
};

// helper fn to calculate the number of slides between the current and the next
$.fn.cycle.hopsFromLast = function(opts, fwd) {
    var hops, l = opts.lastSlide, c = opts.currSlide;
    if (fwd)
        hops = c > l ? c - l : opts.slideCount - l;
    else
        hops = c < l ? l - c : l + opts.slideCount - c;
    return hops;
};

// fix clearType problems in ie6 by setting an explicit bg color
// (otherwise text slides look horrible during a fade transition)
function clearTypeFix($slides) {
    debug('applying clearType background-color hack');
    function hex(s) {
        s = parseInt(s).toString(16);
        return s.length < 2 ? '0'+s : s;
    };
    function getBg(e) {
        for ( ; e && e.nodeName.toLowerCase() != 'html'; e = e.parentNode) {
            var v = $.css(e,'background-color');
            if (v.indexOf('rgb') >= 0 ) {
                var rgb = v.match(/\d+/g);
                return '#'+ hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);
            }
            if (v && v != 'transparent')
                return v;
        }
        return '#ffffff';
    };
    $slides.each(function() { $(this).css('background-color', getBg(this)); });
};

// reset common props before the next transition
$.fn.cycle.commonReset = function(curr,next,opts,w,h,rev) {
    $(opts.elements).not(curr).hide();
    opts.cssBefore.opacity = 1;
    opts.cssBefore.display = 'block';
    if (w !== false && next.cycleW > 0)
        opts.cssBefore.width = next.cycleW;
    if (h !== false && next.cycleH > 0)
        opts.cssBefore.height = next.cycleH;
    opts.cssAfter = opts.cssAfter || {};
    opts.cssAfter.display = 'none';
    $(curr).css('zIndex',opts.slideCount + (rev === true ? 1 : 0));
    $(next).css('zIndex',opts.slideCount + (rev === true ? 0 : 1));
};

// the actual fn for effecting a transition
$.fn.cycle.custom = function(curr, next, opts, cb, fwd, speedOverride) {
    var $l = $(curr), $n = $(next);
    var speedIn = opts.speedIn, speedOut = opts.speedOut, easeIn = opts.easeIn, easeOut = opts.easeOut;
    $n.css(opts.cssBefore);
    if (speedOverride) {
        if (typeof speedOverride == 'number')
            speedIn = speedOut = speedOverride;
        else
            speedIn = speedOut = 1;
        easeIn = easeOut = null;
    }
    var fn = function() {$n.animate(opts.animIn, speedIn, easeIn, cb)};
    $l.animate(opts.animOut, speedOut, easeOut, function() {
        if (opts.cssAfter) $l.css(opts.cssAfter);
        if (!opts.sync) fn();
    });
    if (opts.sync) fn();
};

// transition definitions - only fade is defined here, transition pack defines the rest
$.fn.cycle.transitions = {
    fade: function($cont, $slides, opts) {
        $slides.not(':eq('+opts.currSlide+')').css('opacity',0);
        opts.before.push(function(curr,next,opts) {
            $.fn.cycle.commonReset(curr,next,opts);
            opts.cssBefore.opacity = 0;
        });
        opts.animIn    = { opacity: 1 };
        opts.animOut   = { opacity: 0 };
        opts.cssBefore = { top: 0, left: 0 };
    }
};

$.fn.cycle.ver = function() { return ver; };

// override these globally if you like (they are all optional)
$.fn.cycle.defaults = {
    fx:           'fade', // name of transition effect (or comma separated names, ex: 'fade,scrollUp,shuffle')
    timeout:       4000,  // milliseconds between slide transitions (0 to disable auto advance)
    timeoutFn:     null,  // callback for determining per-slide timeout value:  function(currSlideElement, nextSlideElement, options, forwardFlag)
    continuous:    0,     // true to start next transition immediately after current one completes
    speed:         1000,  // speed of the transition (any valid fx speed value)
    speedIn:       null,  // speed of the 'in' transition
    speedOut:      null,  // speed of the 'out' transition
    next:          null,  // selector for element to use as event trigger for next slide
    prev:          null,  // selector for element to use as event trigger for previous slide
//  prevNextClick: null,  // @deprecated; please use onPrevNextEvent instead
    onPrevNextEvent: null,  // callback fn for prev/next events: function(isNext, zeroBasedSlideIndex, slideElement)
    prevNextEvent:'click.cycle',// event which drives the manual transition to the previous or next slide
    pager:         null,  // selector for element to use as pager container
    //pagerClick   null,  // @deprecated; please use onPagerEvent instead
    onPagerEvent:  null,  // callback fn for pager events: function(zeroBasedSlideIndex, slideElement)
    pagerEvent:   'click.cycle', // name of event which drives the pager navigation
    allowPagerClickBubble: false, // allows or prevents click event on pager anchors from bubbling
    pagerAnchorBuilder: null, // callback fn for building anchor links:  function(index, DOMelement)
    before:        null,  // transition callback (scope set to element to be shown):     function(currSlideElement, nextSlideElement, options, forwardFlag)
    after:         null,  // transition callback (scope set to element that was shown):  function(currSlideElement, nextSlideElement, options, forwardFlag)
    end:           null,  // callback invoked when the slideshow terminates (use with autostop or nowrap options): function(options)
    easing:        null,  // easing method for both in and out transitions
    easeIn:        null,  // easing for "in" transition
    easeOut:       null,  // easing for "out" transition
    shuffle:       null,  // coords for shuffle animation, ex: { top:15, left: 200 }
    animIn:        null,  // properties that define how the slide animates in
    animOut:       null,  // properties that define how the slide animates out
    cssBefore:     null,  // properties that define the initial state of the slide before transitioning in
    cssAfter:      null,  // properties that defined the state of the slide after transitioning out
    fxFn:          null,  // function used to control the transition: function(currSlideElement, nextSlideElement, options, afterCalback, forwardFlag)
    height:       'auto', // container height
    startingSlide: 0,     // zero-based index of the first slide to be displayed
    sync:          1,     // true if in/out transitions should occur simultaneously
    random:        0,     // true for random, false for sequence (not applicable to shuffle fx)
    fit:           0,     // force slides to fit container
    containerResize: 1,   // resize container to fit largest slide
    pause:         0,     // true to enable "pause on hover"
    pauseOnPagerHover: 0, // true to pause when hovering over pager link
    autostop:      0,     // true to end slideshow after X transitions (where X == slide count)
    autostopCount: 0,     // number of transitions (optionally used with autostop to define X)
    delay:         0,     // additional delay (in ms) for first transition (hint: can be negative)
    slideExpr:     null,  // expression for selecting slides (if something other than all children is required)
    cleartype:     !$.support.opacity,  // true if clearType corrections should be applied (for IE)
    cleartypeNoBg: false, // set to true to disable extra cleartype fixing (leave false to force background color setting on slides)
    nowrap:        0,     // true to prevent slideshow from wrapping
    fastOnEvent:   0,     // force fast transitions when triggered manually (via pager or prev/next); value == time in ms
    randomizeEffects: 1,  // valid when multiple effects are used; true to make the effect sequence random
    rev:           0,    // causes animations to transition in reverse
    manualTrump:   true,  // causes manual transition to stop an active transition instead of being ignored
    requeueOnImageNotLoaded: true, // requeue the slideshow if any image slides are not yet loaded
    requeueTimeout: 250,  // ms delay for requeue
    activePagerClass: 'activeSlide', // class name used for the active pager link
    updateActivePagerLink: null, // callback fn invoked to update the active pager link (adds/removes activePagerClass style)
    backwards:     false  // true to start slideshow at last slide and move backwards through the stack
};

})(jQuery);


/*!
 * jQuery Cycle Plugin Transition Definitions
 * This script is a plugin for the jQuery Cycle Plugin
 * Examples and documentation at: http://malsup.com/jquery/cycle/
 * Copyright (c) 2007-2010 M. Alsup
 * Version:  2.72
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
(function($) {

//
// These functions define one-time slide initialization for the named
// transitions. To save file size feel free to remove any of these that you
// don't need.
//
$.fn.cycle.transitions.none = function($cont, $slides, opts) {
    opts.fxFn = function(curr,next,opts,after){
        $(next).show();
        $(curr).hide();
        after();
    };
}

// scrollUp/Down/Left/Right
$.fn.cycle.transitions.scrollUp = function($cont, $slides, opts) {
    $cont.css('overflow','hidden');
    opts.before.push($.fn.cycle.commonReset);
    var h = $cont.height();
    opts.cssBefore ={ top: h, left: 0 };
    opts.cssFirst = { top: 0 };
    opts.animIn   = { top: 0 };
    opts.animOut  = { top: -h };
};
$.fn.cycle.transitions.scrollDown = function($cont, $slides, opts) {
    $cont.css('overflow','hidden');
    opts.before.push($.fn.cycle.commonReset);
    var h = $cont.height();
    opts.cssFirst = { top: 0 };
    opts.cssBefore= { top: -h, left: 0 };
    opts.animIn   = { top: 0 };
    opts.animOut  = { top: h };
};
$.fn.cycle.transitions.scrollLeft = function($cont, $slides, opts) {
    $cont.css('overflow','hidden');
    opts.before.push($.fn.cycle.commonReset);
    var w = $cont.width();
    opts.cssFirst = { left: 0 };
    opts.cssBefore= { left: w, top: 0 };
    opts.animIn   = { left: 0 };
    opts.animOut  = { left: 0-w };
};
$.fn.cycle.transitions.scrollRight = function($cont, $slides, opts) {
    $cont.css('overflow','hidden');
    opts.before.push($.fn.cycle.commonReset);
    var w = $cont.width();
    opts.cssFirst = { left: 0 };
    opts.cssBefore= { left: -w, top: 0 };
    opts.animIn   = { left: 0 };
    opts.animOut  = { left: w };
};
$.fn.cycle.transitions.scrollHorz = function($cont, $slides, opts) {
    $cont.css('overflow','hidden').width();
    opts.before.push(function(curr, next, opts, fwd) {
        $.fn.cycle.commonReset(curr,next,opts);
        opts.cssBefore.left = fwd ? (next.cycleW-1) : (1-next.cycleW);
        opts.animOut.left = fwd ? -curr.cycleW : curr.cycleW;
    });
    opts.cssFirst = { left: 0 };
    opts.cssBefore= { top: 0 };
    opts.animIn   = { left: 0 };
    opts.animOut  = { top: 0 };
};
$.fn.cycle.transitions.scrollVert = function($cont, $slides, opts) {
    $cont.css('overflow','hidden');
    opts.before.push(function(curr, next, opts, fwd) {
        $.fn.cycle.commonReset(curr,next,opts);
        opts.cssBefore.top = fwd ? (1-next.cycleH) : (next.cycleH-1);
        opts.animOut.top = fwd ? curr.cycleH : -curr.cycleH;
    });
    opts.cssFirst = { top: 0 };
    opts.cssBefore= { left: 0 };
    opts.animIn   = { top: 0 };
    opts.animOut  = { left: 0 };
};

// slideX/slideY
$.fn.cycle.transitions.slideX = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $(opts.elements).not(curr).hide();
        $.fn.cycle.commonReset(curr,next,opts,false,true);
        opts.animIn.width = next.cycleW;
    });
    opts.cssBefore = { left: 0, top: 0, width: 0 };
    opts.animIn  = { width: 'show' };
    opts.animOut = { width: 0 };
};
$.fn.cycle.transitions.slideY = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $(opts.elements).not(curr).hide();
        $.fn.cycle.commonReset(curr,next,opts,true,false);
        opts.animIn.height = next.cycleH;
    });
    opts.cssBefore = { left: 0, top: 0, height: 0 };
    opts.animIn  = { height: 'show' };
    opts.animOut = { height: 0 };
};

// shuffle
$.fn.cycle.transitions.shuffle = function($cont, $slides, opts) {
    var i, w = $cont.css('overflow', 'visible').width();
    $slides.css({left: 0, top: 0});
    opts.before.push(function(curr,next,opts) {
        $.fn.cycle.commonReset(curr,next,opts,true,true,true);
    });
    // only adjust speed once!
    if (!opts.speedAdjusted) {
        opts.speed = opts.speed / 2; // shuffle has 2 transitions
        opts.speedAdjusted = true;
    }
    opts.random = 0;
    opts.shuffle = opts.shuffle || {left:-w, top:15};
    opts.els = [];
    for (i=0; i < $slides.length; i++)
        opts.els.push($slides[i]);

    for (i=0; i < opts.currSlide; i++)
        opts.els.push(opts.els.shift());

    // custom transition fn (hat tip to Benjamin Sterling for this bit of sweetness!)
    opts.fxFn = function(curr, next, opts, cb, fwd) {
        var $el = fwd ? $(curr) : $(next);
        $(next).css(opts.cssBefore);
        var count = opts.slideCount;
        $el.animate(opts.shuffle, opts.speedIn, opts.easeIn, function() {
            var hops = $.fn.cycle.hopsFromLast(opts, fwd);
            for (var k=0; k < hops; k++)
                fwd ? opts.els.push(opts.els.shift()) : opts.els.unshift(opts.els.pop());
            if (fwd) {
                for (var i=0, len=opts.els.length; i < len; i++)
                    $(opts.els[i]).css('z-index', len-i+count);
            }
            else {
                var z = $(curr).css('z-index');
                $el.css('z-index', parseInt(z)+1+count);
            }
            $el.animate({left:0, top:0}, opts.speedOut, opts.easeOut, function() {
                $(fwd ? this : curr).hide();
                if (cb) cb();
            });
        });
    };
    opts.cssBefore = { display: 'block', opacity: 1, top: 0, left: 0 };
};

// turnUp/Down/Left/Right
$.fn.cycle.transitions.turnUp = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,true,false);
        opts.cssBefore.top = next.cycleH;
        opts.animIn.height = next.cycleH;
    });
    opts.cssFirst  = { top: 0 };
    opts.cssBefore = { left: 0, height: 0 };
    opts.animIn    = { top: 0 };
    opts.animOut   = { height: 0 };
};
$.fn.cycle.transitions.turnDown = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,true,false);
        opts.animIn.height = next.cycleH;
        opts.animOut.top   = curr.cycleH;
    });
    opts.cssFirst  = { top: 0 };
    opts.cssBefore = { left: 0, top: 0, height: 0 };
    opts.animOut   = { height: 0 };
};
$.fn.cycle.transitions.turnLeft = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,false,true);
        opts.cssBefore.left = next.cycleW;
        opts.animIn.width = next.cycleW;
    });
    opts.cssBefore = { top: 0, width: 0  };
    opts.animIn    = { left: 0 };
    opts.animOut   = { width: 0 };
};
$.fn.cycle.transitions.turnRight = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,false,true);
        opts.animIn.width = next.cycleW;
        opts.animOut.left = curr.cycleW;
    });
    opts.cssBefore = { top: 0, left: 0, width: 0 };
    opts.animIn    = { left: 0 };
    opts.animOut   = { width: 0 };
};

// zoom
$.fn.cycle.transitions.zoom = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,false,false,true);
        opts.cssBefore.top = next.cycleH/2;
        opts.cssBefore.left = next.cycleW/2;
        opts.animIn    = { top: 0, left: 0, width: next.cycleW, height: next.cycleH };
        opts.animOut   = { width: 0, height: 0, top: curr.cycleH/2, left: curr.cycleW/2 };
    });
    opts.cssFirst = { top:0, left: 0 };
    opts.cssBefore = { width: 0, height: 0 };
};

// fadeZoom
$.fn.cycle.transitions.fadeZoom = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,false,false);
        opts.cssBefore.left = next.cycleW/2;
        opts.cssBefore.top = next.cycleH/2;
        opts.animIn = { top: 0, left: 0, width: next.cycleW, height: next.cycleH };
    });
    opts.cssBefore = { width: 0, height: 0 };
    opts.animOut  = { opacity: 0 };
};

// blindX
$.fn.cycle.transitions.blindX = function($cont, $slides, opts) {
    var w = $cont.css('overflow','hidden').width();
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts);
        opts.animIn.width = next.cycleW;
        opts.animOut.left   = curr.cycleW;
    });
    opts.cssBefore = { left: w, top: 0 };
    opts.animIn = { left: 0 };
    opts.animOut  = { left: w };
};
// blindY
$.fn.cycle.transitions.blindY = function($cont, $slides, opts) {
    var h = $cont.css('overflow','hidden').height();
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts);
        opts.animIn.height = next.cycleH;
        opts.animOut.top   = curr.cycleH;
    });
    opts.cssBefore = { top: h, left: 0 };
    opts.animIn = { top: 0 };
    opts.animOut  = { top: h };
};
// blindZ
$.fn.cycle.transitions.blindZ = function($cont, $slides, opts) {
    var h = $cont.css('overflow','hidden').height();
    var w = $cont.width();
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts);
        opts.animIn.height = next.cycleH;
        opts.animOut.top   = curr.cycleH;
    });
    opts.cssBefore = { top: h, left: w };
    opts.animIn = { top: 0, left: 0 };
    opts.animOut  = { top: h, left: w };
};

// growX - grow horizontally from centered 0 width
$.fn.cycle.transitions.growX = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,false,true);
        opts.cssBefore.left = this.cycleW/2;
        opts.animIn = { left: 0, width: this.cycleW };
        opts.animOut = { left: 0 };
    });
    opts.cssBefore = { width: 0, top: 0 };
};
// growY - grow vertically from centered 0 height
$.fn.cycle.transitions.growY = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,true,false);
        opts.cssBefore.top = this.cycleH/2;
        opts.animIn = { top: 0, height: this.cycleH };
        opts.animOut = { top: 0 };
    });
    opts.cssBefore = { height: 0, left: 0 };
};

// curtainX - squeeze in both edges horizontally
$.fn.cycle.transitions.curtainX = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,false,true,true);
        opts.cssBefore.left = next.cycleW/2;
        opts.animIn = { left: 0, width: this.cycleW };
        opts.animOut = { left: curr.cycleW/2, width: 0 };
    });
    opts.cssBefore = { top: 0, width: 0 };
};
// curtainY - squeeze in both edges vertically
$.fn.cycle.transitions.curtainY = function($cont, $slides, opts) {
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,true,false,true);
        opts.cssBefore.top = next.cycleH/2;
        opts.animIn = { top: 0, height: next.cycleH };
        opts.animOut = { top: curr.cycleH/2, height: 0 };
    });
    opts.cssBefore = { left: 0, height: 0 };
};

// cover - curr slide covered by next slide
$.fn.cycle.transitions.cover = function($cont, $slides, opts) {
    var d = opts.direction || 'left';
    var w = $cont.css('overflow','hidden').width();
    var h = $cont.height();
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts);
        if (d == 'right')
            opts.cssBefore.left = -w;
        else if (d == 'up')
            opts.cssBefore.top = h;
        else if (d == 'down')
            opts.cssBefore.top = -h;
        else
            opts.cssBefore.left = w;
    });
    opts.animIn = { left: 0, top: 0};
    opts.animOut = { opacity: 1 };
    opts.cssBefore = { top: 0, left: 0 };
};

// uncover - curr slide moves off next slide
$.fn.cycle.transitions.uncover = function($cont, $slides, opts) {
    var d = opts.direction || 'left';
    var w = $cont.css('overflow','hidden').width();
    var h = $cont.height();
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,true,true,true);
        if (d == 'right')
            opts.animOut.left = w;
        else if (d == 'up')
            opts.animOut.top = -h;
        else if (d == 'down')
            opts.animOut.top = h;
        else
            opts.animOut.left = -w;
    });
    opts.animIn = { left: 0, top: 0 };
    opts.animOut = { opacity: 1 };
    opts.cssBefore = { top: 0, left: 0 };
};

// toss - move top slide and fade away
$.fn.cycle.transitions.toss = function($cont, $slides, opts) {
    var w = $cont.css('overflow','visible').width();
    var h = $cont.height();
    opts.before.push(function(curr, next, opts) {
        $.fn.cycle.commonReset(curr,next,opts,true,true,true);
        // provide default toss settings if animOut not provided
        if (!opts.animOut.left && !opts.animOut.top)
            opts.animOut = { left: w*2, top: -h/2, opacity: 0 };
        else
            opts.animOut.opacity = 0;
    });
    opts.cssBefore = { left: 0, top: 0 };
    opts.animIn = { left: 0 };
};

// wipe - clip animation
$.fn.cycle.transitions.wipe = function($cont, $slides, opts) {
    var w = $cont.css('overflow','hidden').width();
    var h = $cont.height();
    opts.cssBefore = opts.cssBefore || {};
    var clip;
    if (opts.clip) {
        if (/l2r/.test(opts.clip))
            clip = 'rect(0px 0px '+h+'px 0px)';
        else if (/r2l/.test(opts.clip))
            clip = 'rect(0px '+w+'px '+h+'px '+w+'px)';
        else if (/t2b/.test(opts.clip))
            clip = 'rect(0px '+w+'px 0px 0px)';
        else if (/b2t/.test(opts.clip))
            clip = 'rect('+h+'px '+w+'px '+h+'px 0px)';
        else if (/zoom/.test(opts.clip)) {
            var top = parseInt(h/2);
            var left = parseInt(w/2);
            clip = 'rect('+top+'px '+left+'px '+top+'px '+left+'px)';
        }
    }

    opts.cssBefore.clip = opts.cssBefore.clip || clip || 'rect(0px 0px 0px 0px)';

    var d = opts.cssBefore.clip.match(/(\d+)/g);
    var t = parseInt(d[0]), r = parseInt(d[1]), b = parseInt(d[2]), l = parseInt(d[3]);

    opts.before.push(function(curr, next, opts) {
        if (curr == next) return;
        var $curr = $(curr), $next = $(next);
        $.fn.cycle.commonReset(curr,next,opts,true,true,false);
        opts.cssAfter.display = 'block';

        var step = 1, count = parseInt((opts.speedIn / 13)) - 1;
        (function f() {
            var tt = t ? t - parseInt(step * (t/count)) : 0;
            var ll = l ? l - parseInt(step * (l/count)) : 0;
            var bb = b < h ? b + parseInt(step * ((h-b)/count || 1)) : h;
            var rr = r < w ? r + parseInt(step * ((w-r)/count || 1)) : w;
            $next.css({ clip: 'rect('+tt+'px '+rr+'px '+bb+'px '+ll+'px)' });
            (step++ <= count) ? setTimeout(f, 13) : $curr.css('display', 'none');
        })();
    });
    opts.cssBefore = { display: 'block', opacity: 1, top: 0, left: 0 };
    opts.animIn    = { left: 0 };
    opts.animOut   = { left: 0 };
};

})(jQuery);

