/* http://keith-wood.name/imageCube.html
   Image Cube for jQuery v1.2.0.
   Written by Keith Wood (kbwood{at}iinet.com.au) June 2008.
   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
   Please attribute the author if you use it. */

/* Rotate images (or other things) as if on the faces of a cube.
   $('div selector').imagecube();
   Or with options like:
   $('div selector').imagecube({direction: 'left', speed: 1000});
*/

(function($) { // Hide scope, no $ conflict

/* Image cube manager. */
function ImageCube() {
	this._defaults = {
		direction: 'random', // Direction of rotation: random|up|down|left|right
		randomSelection: ['up', 'down', 'left', 'right'], // If direction is random, select one of these
		speed: 2000, // Time taken (milliseconds) to transition
		easing: 'linear', // Name of the easing to use during transitions
		repeat: true, // True to automatically trigger a new transition after a pause
		pause: 2000, // Time (milliseconds) between transitions
		selection: 'forward', // How to choose the next item to show:
			// 'forward', 'backward', 'random'
		shading: true, // True to add shading effects, false for no effects
		opacity: 0.8, // Maximum opacity (0.0 - 1.0) for highlights and shadows
		imagePath: '', // Any extra path to locate the highlight/shadow images
		full3D: true, // True to add cubic perspective, false for 2D rotation
		segments: 20, // The number of segments that make up each 3D face
		reduction: 30, // The amount (pixels) of reduction for far edges of the cube
		expansion: 10, // The amount (pixels) of expansion for the near edge of the cube
		lineHeight: [0.0, 1.25], // Hidden and normal line height (em) for text
		letterSpacing: [-0.4, 0.0], // Hidden and normal letter spacing (em) for text
		beforeRotate: null, // Callback before rotating
		afterRotate: null // Callback after rotating
	};
};

var UP = 0;
var DOWN = 1;
var LEFT = 2;
var RIGHT = 3;

var PROP_NAME = 'imageCube';

$.extend(ImageCube.prototype, {
	/* Class name added to elements to indicate already configured with image cube. */
	markerClassName: 'hasImageCube',

	/* Override the default settings for all image cube instances.
	   @param  options  (object) the new settings to use as defaults */
	setDefaults: function(options) {
		extendRemove(this._defaults, options || {});
	},

	/* Attach the image cube functionality to a div.
	   @param  target   (element) the containing division
	   @param  options  (object) the settings for this image cube instance (optional) */
	_attachImageCube: function(target, options) {
		target = $(target);
		if (target.hasClass(this.markerClassName)) {
			return;
		}
		var allOptions = $.extend({_position: target.css('position')},
			this._defaults, options || {});
		$.data(target[0], PROP_NAME, allOptions);
		target.addClass(this.markerClassName).css({position: 'relative'}).
			children().each(function() {
				var child = $(this);
				$.data(this, PROP_NAME,
					{width: child.css('width'), height: child.css('height'),
					position: child.css('position'), lineHeight: child.css('lineHeight'),
					letterSpacing: child.css('letterSpacing')});
				child.css({width: target.css('width'), height: target.css('height'),
					position: 'absolute', lineHeight: allOptions.lineHeight[1],
					letterSpacing: allOptions.letterSpacing[1]});
			}).not(':first').hide();
		this._prepareRotation(target[0]);
	},

	/* Note current visible child and schedule a repeat rotation (if required).
	   @param  target  (element) the containing division */
	_prepareRotation: function(target) {
		target = $(target);
		target.children('.imageCubeShading,.imageCubeFrom,.imageCubeTo').remove();
		var options = $.data(target[0], PROP_NAME);
		options.current = target.children(':visible')[0];
		var randomSelection = function(collection) {
			return (!collection.length ? collection : collection.filter(
				':eq(' + Math.floor(Math.random() * collection.length) + ')'));
		};
		options.next = (options.selection == 'random' ?
			randomSelection(target.children(':hidden')) :
			(options.selection == 'backward' ? $(options.current).prev() :
			$(options.current).next()));
		options.next = (options.next.length ? options.next :
			(options.selection == 'random' ? options.current :
			(options.selection == 'backward' ? target.children(':last') :
			target.children(':first'))))[0]; // Cycle around if at the end
		if (options.repeat && !options._timer) {
			options._timer = setTimeout(function() {
					$.imagecube._rotateImageCube(target[0]); },
				options.pause);
		}
		$.data(target[0], PROP_NAME, options);
	},

	/* Rotate the image cube to the next face.
	   @param  target    (element) the containing division
	   @param  callback  (function) a function to call when finished with the rotation (optional) */
	_rotateImageCube: function(target, callback) {
		target = $(target);
		this._stopImageCube(target[0], true);
		var options = $.data(target[0], PROP_NAME);
		var callbackArgs = [options.current, options.next];
		if (options.beforeRotate) {
			options.beforeRotate.apply(target[0], callbackArgs);
		}
		var animTo = {};
		animTo[PROP_NAME] = 1.0;
		target.attr(PROP_NAME, 0.0).animate(animTo, options.speed, options.easing,
			function() {
				if (options.afterRotate) {
					options.afterRotate.apply(target[0], callbackArgs);
				}
				if (callback) {
					callback.apply(target[0]);
				}
			});
	},

	/* Retrieve the currently visible child of an image cube div.
	   @param  target  (element) the containing division
	   @return  (element) the currently displayed child of target division */
	_currentImageCube: function(target) {
		return ($(target).hasClass(this.markerClassName) ?
			$.data(target, PROP_NAME).current : null);
	},

	/* Retrieve the next visible child of an image cube div.
	   @param  target  (element) the containing division
	   @return  (element) the next to be displayed child of target division */
	_nextImageCube: function(target) {
		return ($(target).hasClass(this.markerClassName) ?
			$.data(target, PROP_NAME).next : null);
	},

	/* Stop the image cube automatically rotating to the next face.
	   @param  target     (element) the containing division
	   @param  timerOnly  (boolean) true if only temporarily stopping (optional) */
	_stopImageCube: function(target, timerOnly) {
		var options = $.data(target, PROP_NAME);
		if (options._timer) {
			clearTimeout(options._timer);
			options._timer = null;
		}
		if (!timerOnly) {
			options.repeat = false;
		}
		$.data(target, PROP_NAME, options);
	},

	/* Start the image cube automatically rotating to the next face.
	   @param  target  (element) the containing division */
	_startImageCube: function(target) {
		this._changeImageCube(target, {repeat: true});
	},

	/* Reconfigure the settings for an image cube div.
	   @param  target   (element) the containing division
	   @param  options  (object) the new settings for this image cube instance or
	                    (string) the name of the setting
	   @param  value    (any, optional) the value of the setting */
	_changeImageCube: function(target, options, value) {
		if (typeof options == 'string') {
			var opts = {};
			opts[options] = value;
			options = opts;
		}
		var curOptions = $.data(target, PROP_NAME);
		extendRemove(curOptions || {}, options || {});
		$.data(target, PROP_NAME, curOptions);
		this._prepareRotation(target);
	},

	/* Remove the image cube functionality from a div.
	   @param  target  (element) the containing division */
	_destroyImageCube: function(target) {
		target = $(target);
		if (!target.hasClass(this.markerClassName)) {
			return;
		}
		this._stopImageCube(target[0]);
		var options = $.data(target[0], PROP_NAME);
		target.stop().css({position: options._position}).
			removeClass(this.markerClassName).
			children('.imageCubeShading,.imageCubeFrom,.imageCubeTo').remove();
		target.children().each(function() {
			$(this).css($.data(this, PROP_NAME)).show();
			$.removeData(this, PROP_NAME);
		});
		$.removeData(target[0], PROP_NAME);
	},

	/* Prepare the image cube for animation.
	   @param  target  (element) the containing division */
	_prepareAnimation: function(target) {
		var options = $.data(target, PROP_NAME);
		var target = $(target);
		var offset = {left: 0, top: 0};
		target.parents().each(function() { // Check if this area is fixed
			var $this = $(this);
			if ($this.css('position') == 'fixed') {
				offset.left -= $this.offset().left;
				offset.top -= $this.offset().top;
				return false;
			}
		});
		var dims = {width: target.width(), height: target.height()};
		var direction = (options.direction != 'random' ? options.direction :
			options.randomSelection[Math.floor(Math.random() * options.randomSelection.length)]);
		direction = Math.max(0, $.inArray(direction, ['up', 'down', 'left', 'right']));
		options._curDirection = direction;
		var upDown = (direction == UP || direction == DOWN);
		var leftRight = (direction == LEFT || direction == RIGHT);
		var upLeft = (direction == UP || direction == LEFT);
		var firstOpacity = (upLeft ? 0 : options.opacity);
		var pFrom = $(options.current);
		var pTo = $(options.next);
		// Calculate borders and padding for both elements
		var border = [];
		var parseBorders = function(p) {
			var b = [0, 0, 0, 0];
			if (!$.browser.msie || p.css('border')) {
				for (var i = 0; i < 4; i++) {
					b[i] = p.css('border' + ['Left', 'Right', 'Top', 'Bottom'][i] + 'Width');
					var value = parseFloat(b[i]);
					b[i] = (!isNaN(value) ? value :
						Math.max(0, $.inArray(b[i], ['thin', 'medium', 'thick']) * 2 + 1));
				}
			}
			return b;
		};
		border[0] = parseBorders(pFrom);
		border[1] = parseBorders(pTo);
		var pad = [];
		pad[0] = [parseFloat(pFrom.css('padding-left')), parseFloat(pFrom.css('padding-right')),
			parseFloat(pFrom.css('padding-top')), parseFloat(pFrom.css('padding-bottom'))];
		pad[1] = [parseFloat(pTo.css('padding-left')), parseFloat(pTo.css('padding-right')),
			parseFloat(pTo.css('padding-top')), parseFloat(pTo.css('padding-bottom'))];
		var extras = [];
		extras[0] = [($.boxModel ? border[0][0] + border[0][1] + pad[0][0] + pad[0][1] : 0),
			($.boxModel ? border[0][2] + border[0][3] + pad[0][2] + pad[0][3] : 0)];
		extras[1] = [($.boxModel ? border[1][0] + border[1][1] + pad[1][0] + pad[1][1] : 0),
			($.boxModel ? border[1][2] + border[1][3] + pad[1][2] + pad[1][3] : 0)];
		// Define the property ranges per element
		var stepProps = [];
		stepProps[0] = {elem: pFrom, // Currently displayed element
			left: {start: offset.left,
				end: offset.left + (direction == RIGHT ? dims.width : 0), units: 'px'},
			width: {start: dims.width - extras[0][0],
				end: (upDown ? dims.width - extras[0][0] : 0), units: 'px'},
			top: {start: offset.top,
				end: offset.top + (direction == DOWN ? dims.height : 0), units: 'px'},
			height: {start: dims.height - extras[0][1],
				end: (upDown ? 0 : dims.height - extras[0][1]), units: 'px'},
			paddingLeft: {start: pad[0][0], end: (leftRight ? 0 : pad[0][0]), units: 'px'},
			paddingRight: {start: pad[0][1], end: (leftRight ? 0 : pad[0][1]), units: 'px'},
			paddingTop: {start: pad[0][2], end: (upDown ? 0 : pad[0][2]), units: 'px'},
			paddingBottom: {start: pad[0][3], end: (upDown ? 0 : pad[0][3]), units: 'px'},
			borderLeftWidth: {start: border[0][0], end: (leftRight ? 0 : border[0][0]), units: 'px'},
			borderRightWidth: {start: border[0][1], end: (leftRight ? 0 : border[0][1]), units: 'px'},
			borderTopWidth: {start: border[0][2], end: (upDown ? 0 : border[0][2]), units: 'px'},
			borderBottomWidth: {start: border[0][3], end: (upDown ? 0 : border[0][3]), units: 'px'},
			lineHeight: {start: options.lineHeight[1],
				end: (upDown ? options.lineHeight[0] : options.lineHeight[1]), units: 'em'},
			letterSpacing: {start: options.letterSpacing[1],
				end: (upDown ? options.letterSpacing[1] : options.letterSpacing[0]), units: 'em'}};
		stepProps[1] = {elem: pTo, // New element to be displayed
			left: {start: offset.left + (direction == LEFT ? dims.width : 0),
				end: offset.left, units: 'px'},
			width: {start: (upDown ? dims.width - extras[1][0] : 0),
				end: dims.width - extras[1][0], units: 'px'},
			top: {start: offset.top + (direction == UP ? dims.height : 0),
				end: offset.top, units: 'px'},
			height: {start: (upDown ? ($.browser.msie ? 1 : 0) : dims.height - extras[1][1]),
				end : dims.height - extras[1][1], units: 'px'},
			paddingLeft: {start: (leftRight ? 0 : pad[1][0]), end: pad[1][0], units: 'px'},
			paddingRight: {start: (leftRight ? 0 : pad[1][1]), end: pad[1][1], units: 'px'},
			paddingTop: {start: (upDown ? 0 : pad[1][2]), end: pad[1][2], units: 'px'},
			paddingBottom: {start: (upDown ? 0 : pad[1][3]), end: pad[1][3], units: 'px'},
			borderLeftWidth: {start: (leftRight ? 0 : border[1][0]), end: border[1][0], units: 'px'},
			borderRightWidth: {start: (leftRight ? 0 : border[1][1]), end: border[1][1], units: 'px'},
			borderTopWidth: {start: (upDown ? 0 : border[1][2]), end: border[1][2], units: 'px'},
			borderBottomWidth: {start: (upDown ? 0 : border[1][3]), end: border[1][3], units: 'px'},
			lineHeight: {start: (upDown ? options.lineHeight[0] : options.lineHeight[1]),
				end: options.lineHeight[1], units: 'em'},
			letterSpacing: {start: (upDown ? options.letterSpacing[1] : options.letterSpacing[0]),
				end: options.letterSpacing[1], units: 'em'}};
		if (options.shading) {
			// Initialise highlight and shadow objects (or colours on IE)
			var setHighShad = function(props, startOpacity, endOpacity) {
				return {left: {start: props.left.start, end: props.left.end, units: 'px'},
					width: {start: props.width.start, end: props.width.end, units: 'px'},
					top: {start: props.top.start, end: props.top.end, units: 'px'},
					height: {start: props.height.start, end: props.height.end, units: 'px'},
					paddingLeft: {start: props.paddingLeft.start + props.borderLeftWidth.start,
						end: props.paddingLeft.end + props.borderLeftWidth.end, units: 'px'},
					paddingRight: {start: props.paddingRight.start + props.borderRightWidth.start,
						end: props.paddingRight.end + props.borderRightWidth.end, units: 'px'},
					paddingTop: {start: props.paddingTop.start + props.borderTopWidth.start,
						end: props.paddingTop.end + props.borderTopWidth.end, units: 'px'},
					paddingBottom: {start: props.paddingBottom.start + props.borderBottomWidth.start,
						end: props.paddingBottom.end + props.borderBottomWidth.end, units: 'px'},
					opacity: {start: startOpacity, end: endOpacity, units: ''}};
			};
			stepProps[2] = setHighShad(stepProps[upLeft ? 0 : 1], // Highlight shading (up/left)
				firstOpacity, options.opacity - firstOpacity);
			stepProps[3] = setHighShad(stepProps[upLeft ? 1 : 0], // Shadow shading (down/right)
				options.opacity - firstOpacity, firstOpacity);
			stepProps[2].elem = $(($.browser.msie ? '<img src="' + options.imagePath + 'imageCubeHigh.png"' :
				'<div') + ' class="imageCubeShading" style="background-color: white; opacity: ' +
				firstOpacity + '; z-index: 10; position: absolute;"' +
				($.browser.msie ? '/>' : '></div>'));
			stepProps[3].elem = $(($.browser.msie ? '<img src="' + options.imagePath + 'imageCubeShad.png"' :
				'<div') + ' class="imageCubeShading" style="background-color: black; opacity: ' +
				(options.opacity - firstOpacity) + '; z-index: 10; position: absolute;"' +
				($.browser.msie ? '/>' : '></div>'));
		}
		// Set up full 3D rotation
		if (options.full3D) {
			for (var i = 0; i < options.segments; i++) {
				target.append(pFrom.clone().addClass('imageCubeFrom').
					css({position: 'absolute', overflow: 'hidden'}));
				if (options.shading) {
					target.append(stepProps[upLeft ? 2 : 3].elem.clone());
				}
			}
			for (var i = 0; i < options.segments; i++) {
				target.append(pTo.clone().addClass('imageCubeTo').
					css({display: 'none', position: 'absolute', width: 0, overflow: 'hidden'}));
				if (options.shading) {
					target.append(stepProps[upLeft ? 3 : 2].elem.clone());
				}
			}
			pFrom.hide();
			pTo.css({width: dims.width - extras[1][0], height: dims.height - extras[1][1]});
		}
		else {
			// Initialise from and to objects
			var initCSS = function(props) {
				return {left: props.left.start + 'px', width: props.width.start + 'px',
					top: props.top.start + 'px', height: props.height.start + 'px',
					lineHeight: props.lineHeight.start + 'em',
					padding: props.paddingTop.start + 'px ' + props.paddingRight.start + 'px ' +
					props.paddingBottom.start + 'px ' + props.paddingLeft.start + 'px',
					borderLeftWidth: props.borderLeftWidth.start + 'px',
					borderRightWidth: props.borderRightWidth.start + 'px',
					borderTopWidth: props.borderTopWidth.start + 'px',
					borderBottomWidth: props.borderBottomWidth.start + 'px',
					letterSpacing: props.letterSpacing.start + 'em', overflow: 'hidden'};
			};
			pFrom.css(initCSS(stepProps[0]));
			pTo.css(initCSS(stepProps[1])).show();
			if (options.shading) {
				target.append(stepProps[2].elem).append(stepProps[3].elem);
			}
		}
		// Pre-compute differences
		for (var i = 0; i < stepProps.length; i++) {
			for (var name in stepProps[i]) {
				var prop = stepProps[i][name];
				prop.diff = prop.end - prop.start;
			}
		}
		return stepProps;
	},

	/* Draw one panel of the 3D perspective view of the cube.
	   @param  target     (element) the container
	   @param  pos        (number) the current position (0.0 - 1.0)
	   @param  stepProps  (object[]) details about the items being animated
	   @return  (boolean) true if drawn in 3D, false if not */
	_drawFull3D: function(target, pos, stepProps) {
		var options = $.data(target, PROP_NAME);
		if (!options.full3D) {
			return false;
		}
		var target = $(target);
		var direction = options._curDirection;
		var upDown = (direction == UP || direction == DOWN);
		var upLeft = (direction == UP || direction == LEFT);
		var width = target.width();
		var height = target.height();
		if (width == 0 || height == 0) {
			return true;
		}
		var current = (1 - pos) * (upDown ? height : width);
		var segments = options.segments;
		var maxExpand = options.expansion * (1 - Math.abs(2 * current - (upDown ? height : width)) /
			(upDown ? height : width));
		var maxReduce = options.reduction - (options.reduction * current / (upDown ? height : width));
		var update = function(className, al, at, bl, bt, cl, ct, dl, dt, opacity, props, attr) {
			var ws = [bl - al, cl - dl];
			var w = Math.max(ws[0], ws[1]);
			var hs = [dt - at, ct - bt];
			var h = Math.max(hs[0], hs[1]);
			var wStep = (upDown ? (ws[0] - ws[1]) / (segments - 1) / 2 : w / segments);
			var hStep = (upDown ? h / segments : (hs[0] - hs[1]) / (segments - 1) / 2);
			var pbw = props.paddingLeft[attr] + props.paddingRight[attr] +
				props.borderLeftWidth[attr] + props.borderRightWidth[attr];
			var pbh = props.paddingTop[attr] + props.paddingBottom[attr] +
				props.borderTopWidth[attr] + props.borderBottomWidth[attr];
			var ral = Math.round(al);
			var rat = Math.round(at);
			var thisLeft = ral;
			var thisTop = rat;
			target.children(className).each(function(i) {
				var nextLeft = Math.round(al + (i + 1) * wStep);
				var nextTop = Math.round(at + (i + 1) * hStep);
				var wCur = ws[0] - (upDown ? 2 * i * wStep : 0);
				var hCur = hs[0] - (upDown ? 0 : 2 * i * hStep);
				$(this).css({display: 'block',
					left: (upDown ? thisLeft : al), top: (upDown ? at : thisTop),
					width: Math.max(0, wCur - pbw), height: Math.max(0, hCur - pbh),
					letterSpacing: (upDown ? wCur / w * (options.letterSpacing[1] -
						options.letterSpacing[0]) + options.letterSpacing[0] :
						pos * props.letterSpacing.diff + props.letterSpacing.start) +
						props.letterSpacing.units,
					lineHeight: (!upDown ? hCur / h * (options.lineHeight[1] -
						options.lineHeight[0]) + options.lineHeight[0] :
						pos * props.lineHeight.diff + props.lineHeight.start) +
						props.lineHeight.units,
					clip: 'rect(' + (!upDown ? 'auto' : (thisTop - rat) + 'px') + ',' +
					(upDown ? 'auto' : (nextLeft - ral) + 'px') + ',' +
					(!upDown ? 'auto' : (nextTop - rat) + 'px') + ',' +
					(upDown ? 'auto' : (thisLeft - ral) + 'px') + ')'});
				if (options.shading) {
					$(this).next().css({left: thisLeft, top: thisTop,
						width: (upDown ? ws[0] - 2 * i * wStep : nextLeft - thisLeft),
						height: (upDown ? nextTop - thisTop : hs[0] - 2 * i * hStep),
						opacity: opacity});
				}
				thisLeft = nextLeft;
				thisTop = nextTop;
			});
		};
		update('.imageCubeFrom',
			[maxReduce, -maxExpand, 0, width - current][direction], // top left
			[0, height - current, maxReduce, -maxExpand][direction],
			[width - maxReduce, width + maxExpand, current, width][direction], // top right
			[0, height - current, -maxExpand, maxReduce][direction],
			[width + maxExpand, width - maxReduce, current, width][direction], // bottom right
			[current, height, height + maxExpand, height - maxReduce][direction],
			[-maxExpand, maxReduce, 0, width - current][direction], // bottom left
			[current, height, height - maxReduce, height + maxExpand][direction],
			(!options.shading ? 0 : (upLeft ? pos : 1 - pos) *
			stepProps[2].opacity.diff + stepProps[2].opacity.start), stepProps[0], 'start');
		update('.imageCubeTo',
			[-maxExpand, options.reduction - maxReduce, current, 0][direction], // top left
			[current, 0, -maxExpand, options.reduction - maxReduce][direction],
			[width + maxExpand, width - (options.reduction - maxReduce), width, width - current][direction], // top right
			[current, 0, options.reduction - maxReduce, -maxExpand][direction],
			[width - (options.reduction - maxReduce), width + maxExpand, width, width - current][direction], // bottom right
			[height, height - current, height - (options.reduction - maxReduce), height + maxExpand][direction],
			[options.reduction - maxReduce, -maxExpand, current, 0][direction], // bottom left
			[height, height - current, height + maxExpand, height - (options.reduction - maxReduce)][direction],
			(!options.shading ? 0 : (upLeft ? pos : 1 - pos) *
			stepProps[3].opacity.diff + stepProps[3].opacity.start), stepProps[1], 'end');
		return true;
	}
});

/* jQuery extend now ignores nulls!
   @param  target  (object) the object to extend
   @param  props   (object) the attributes to modify
   @return  (object) the updated target */
function extendRemove(target, props) {
	$.extend(target, props);
	for (var name in props) {
		if (props[name] == null) {
			target[name] = null;
		}
	}
	return target;
}

/* Attach the image cube functionality to a jQuery selection.
   @param  command  (string) the command to run (optional, default 'attach')
   @param  options  (object) the new settings to use for these image cube instances
   @return  (jQuery) for chaining further calls */
$.fn.imagecube = function(options) {
	var otherArgs = Array.prototype.slice.call(arguments, 1);
	if (options == 'current' || options == 'next') {
		return $.imagecube['_' + options + 'ImageCube'].
			apply($.imagecube, [this[0]].concat(otherArgs));
	}
	return this.each(function() {
		if (typeof options == 'string') {
			$.imagecube['_' + options + 'ImageCube'].
				apply($.imagecube, [this].concat(otherArgs));
		}
		else {
			$.imagecube._attachImageCube(this, options);
		}
	});
};

/* Enable synchronised animation for all of the image cube properties.
   @param  fx  (object) the effects instance to animate */
$.fx.step[PROP_NAME] = function(fx) {
	if (fx.state == 0 || !fx.stepProps) { // Initialisation
		fx.start = 0.0;
		fx.end = 1.0;
		fx.stepProps = $.imagecube._prepareAnimation(fx.elem);
		fx.saveCSS = {borderLeftWidth: fx.stepProps[0].elem.css('borderLeftWidth'),
			borderRightWidth: fx.stepProps[0].elem.css('borderRightWidth'),
			borderTopWidth: fx.stepProps[0].elem.css('borderTopWidth'),
			borderBottomWidth: fx.stepProps[0].elem.css('borderBottomWidth'),
			padding: fx.stepProps[0].elem.css('padding')};
	}

	if (!$.imagecube._drawFull3D(fx.elem, fx.pos, fx.stepProps)) {
		for (var i = 0; i < fx.stepProps.length; i++) { // Update all elements
			var newValues = {};
			for (var name in fx.stepProps[i]) { // Update all properties
				var prop = fx.stepProps[i][name];
				if (name != 'elem') {
					newValues[name] = (fx.pos * prop.diff + prop.start) + prop.units;
				}
			}
			fx.stepProps[i].elem.css(newValues);
		}
	}

	if (fx.state == 1) { // Tidy up afterwards
		fx.stepProps[0].elem.hide().css(fx.saveCSS);
		fx.stepProps[1].elem.show();
		$.imagecube._prepareRotation(fx.elem);
	}
};

/* Initialise the image cube functionality. */
$.imagecube = new ImageCube(); // singleton instance

})(jQuery);

