/*!
 * Parallaxify.js v0.0.3
 * http://hwthorn.github.io/parallaxify
 *
 * Copyright 2016, Felix Pflaum
 * Released under the MIT license
 * http://hwthorn.mit-license.org
 *
 * Based on stellar.js by Mark Dalgleish
 * http://markdalgleish.com/projects/stellar.js
 */

 ;(function($, window, document, undefined) {

	var pluginName = 'parallaxify',
		maxFPS = 30,
		defaults = {
			positionProperty: 'position',
			horizontalParallax: true,
			verticalParallax: true,
			parallaxBackgrounds: true,
			parallaxElements: true,
			responsive: false,
			useMouseMove: true,
			useGyroscope: true,
			alphaFilter: 0.9,			// use Low Pass Filter to smooth sensor readings (1 = no filter)
			motionType: 'natural',
			mouseMotionType: 'gaussian',
			inputPriority: 'mouse',		// define which input device has priority over the other 'mouse' or 'gyroscope'
			motionAngleX: 80,			// (0 < motionAngle < 90) delta angle that is used to render max parallax in this direction
			motionAngleY: 80,
			adjustBasePosition: true,	// using Low Pass Filter to adjust base position
			alphaPosition: 0.05			// alpha for Low Pass Filter used to adjust average position
		},

		// Options for positioning of elements
		// e.g. using css positioning with top and left
		// or using css transforms
		positionProperty = {
			position: {
				setLeft: function($element, left) { $element.css('left', left); },
				setTop: function($element, top) { $element.css('top', top); }
			},
			transform: {
				setPosition: function($element, left, originalLeft, top, originalTop) {
					$element[0].style[prefixedTransform] = 'translate3d(' + (left - originalLeft) + 'px, ' + (top - originalTop) + 'px, 0)';
				}
			}
		},

		// approximation of Gaussian cumulative distribution function with parameter a to influence standard deviation sigma
		gCDFApprox = function(x, a) {
			return 1 / (1 + Math.exp(-(0.07056*a*(x^3))-(1.5976*a*x)));
		},

		// low pass filter for motion events (http://en.wikipedia.org/wiki/Low-pass_filter)
		lowPassFilter = function(curSignal, prevSignal, alpha) {
			if (prevSignal === null) return curSignal;
			if (typeof alpha === "undefined") alpha = 0.5;
			return (alpha * curSignal) + ((1 - alpha) * prevSignal);
		},

		// cached variable to speed up motionTypes
		factorCache = [],

		// Options for calculating the parallax effect
		// naturally a tangent is used, alternatively a Gaussian cumulative distribution function can be used
		motionType = {
			linear: function(delta, deltaMax) {
				if (delta <= -deltaMax) return 1;
				if (delta >= deltaMax) return -1;
				return -delta/deltaMax;
			},
			natural: function(delta, deltaMax) {
				if (delta <= -deltaMax) return 1;
				if (delta >= deltaMax) return -1;
				if (factorCache['n'+deltaMax] === undefined) factorCache['n'+deltaMax] = Math.tan(deltaMax*0.01745);
				return -Math.tan(delta*0.01745) / factorCache['n'+deltaMax];
			},
			performance: function(delta, deltaMax) {
				if (delta <= -deltaMax) return 1;
				if (delta >= deltaMax) return -1;
				if (factorCache['p'+deltaMax] === undefined) factorCache['p'+deltaMax] = (deltaMax/90) + (4.2*Math.pow(deltaMax/90,7));
				return -((delta/90) + (4.2*Math.pow(delta/90,7))) / factorCache['p'+deltaMax];
			},
			gaussian: function(delta, deltaMax) {
				return 1 - 2 * gCDFApprox(delta/90, 135/deltaMax);
			}
		},

		// Returns a function which adds a vendor prefix to any CSS property name
		vendorPrefix = (function() {
			var prefixes = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/,
				style = $('script')[0].style,
				prefix = '',
				prop;

			for (prop in style) {
				if (prefixes.test(prop)) {
					prefix = prop.match(prefixes)[0];
					break;
				}
			}

			if ('WebkitOpacity' in style) { prefix = 'Webkit'; }
			if ('KhtmlOpacity' in style) { prefix = 'Khtml'; }

			return function(property) {
				return prefix + (prefix.length > 0 ? property.charAt(0).toUpperCase() + property.slice(1) : property);
			};
		}()),

		prefixedTransform = vendorPrefix('transform'),

		supportsBackgroundPositionXY = $('<div />', { style: 'background:#fff' }).css('background-position-x') !== undefined,

		setBackgroundPosition = (supportsBackgroundPositionXY ?
			function($element, x, y) {
				$element.css({
					'background-position-x': x,
					'background-position-y': y
				});
			} :
			function($element, x, y) {
				$element.css('background-position', x + ' ' + y);
			}
		),

		getBackgroundPosition = (supportsBackgroundPositionXY ?
			function($element) {
				return [
					$element.css('background-position-x'),
					$element.css('background-position-y')
				];
			} :
			function($element) {
				return $element.css('background-position').split(' ');
			}
		),

		// using requestAnimationFrame to limit execution to
		// the animation frames and reduce browser load
		// fallback to 60x per second
		requestAnimationFrame = (
			window.requestAnimationFrame       ||
			window.webkitRequestAnimationFrame ||
			window.mozRequestAnimationFrame    ||
			window.oRequestAnimationFrame      ||
			window.msRequestAnimationFrame     ||
			function(callback) {
				setTimeout(callback, 1000 / maxFPS);
			}
		);

	function Plugin(element, options) {
		this.element = element;
		this.options = $.extend({}, defaults, options);

		this._defaults = defaults;
		this._name = pluginName;

		this.init();
	}

	Plugin.prototype = {
		init: function() {
			this.name = pluginName + '_' + Math.floor(Math.random() * 1e9);
			this.tilt = {
				beta : 0,
				gamma : 0
			};

			this._defineElements();
			this._defineGetters();
			this._defineSetters();
			this._detectMobile();
			this._detectMotionType();
			this._detectViewport();
			this._handleWindowLoadAndResize();

			this.refresh({ firstLoad: true });

			this._startAnimation();
		},
		_defineElements: function() {
			this.$element = ( this.element === document.body || this.element === window ? $('body') : $(this.element) );
			this.$viewportElement = $(window);
		},
		_defineGetters: function() {

		// define getters
		// sensor data and mouse move
		// return value is between -1 and +1

			var self = this,
				motionTypeAdapter = motionType[self.options.motionType],
				mouseMoveAdapter = motionType[self.options.mouseMotionType];

			this._getMoveHorizontal = function() {

				if (this.useMouseMove && this.clientX !== null && this.clientX !== this.oldClientX ) {
					return mouseMoveAdapter( this.options.motionAngleX * (1 - (2*this.clientX/this.viewportWidth)), this.options.motionAngleX);
				}

				if (this.useSensor && this.beta !== null && this.gamma !== null) {
				// output = 2*gCDFApprox(tilt/180, 0.75*90/(0.5*this.options.motionAngle))-1;
				// -180 < tilt < 180 => multiply beta x 2

					// tilt.gamma and tilt.beta
					var tilt = this.tilt;

					if (this.viewportLandscape) {
						if (this.viewportFlipped) {
						// landscape flipped
							return motionTypeAdapter(-tilt.beta, this.options.motionAngleX);
						} else {
						// landscape regular
							return motionTypeAdapter(tilt.beta, this.options.motionAngleX);
						}
					} else {
						if (this.viewportFlipped) {
						// portrait flipped
							return motionTypeAdapter(-tilt.gamma, this.options.motionAngleX);
						} else {
						// portrait regular
							return motionTypeAdapter(tilt.gamma, this.options.motionAngleX);
						}
					}
				} else {
					this.useSensor = false;
				}

				return mouseMoveAdapter( this.options.motionAngleX * (1 - (2*this.oldClientX/this.viewportWidth)), this.options.motionAngleX);
			};

			this._getMoveVertical = function() {

				if (this.options.useMouseMove && this.clientY !== null && this.clientY !== this.oldClientY ) {
					return mouseMoveAdapter( this.options.motionAngleY * (1 - (2*this.clientY/this.viewportHeight)), this.options.motionAngleY);
				}

				if (this.useSensor && this.beta !== null && this.gamma !== null) {

					// tilt.gamma and tilt.beta
					var tilt = this.tilt;

					if (this.viewportLandscape) {
						if (this.viewportFlipped) {
						// landscape flipped
							return motionTypeAdapter(-tilt.gamma, this.options.motionAngleY);
						} else {
						// landscape regular
							return motionTypeAdapter(tilt.gamma, this.options.motionAngleY);
						}
					} else {
						if (this.viewportFlipped) {
						// portrait flipped
							return motionTypeAdapter(-tilt.beta, this.options.motionAngleY);
						} else {
						// portrait regular
							return motionTypeAdapter(tilt.beta, this.options.motionAngleY);
						}
					}
				} else {
					this.useSensor = false;
				}

				return mouseMoveAdapter( this.options.motionAngleY * (1 - (2*this.oldClientY/this.viewportHeight)), this.options.motionAngleY);
			};

		},
		_defineSetters: function() {

		// define setters
		// set position of elements

			var self = this,
				positionPropertyAdapter = positionProperty[self.options.positionProperty];

			// use .setPosition or if not available use .setLeft and .setTop
			this._setPosition = positionPropertyAdapter.setPosition ||
				function($element, left, originalLeft, top, originalTop) {
					if (self.options.horizontalParallax) {
						positionPropertyAdapter.setLeft($element, left, originalLeft);
					}
					if (self.options.verticalParallax) {
						positionPropertyAdapter.setTop($element, top, originalTop);
					}
				};

		},
		refresh: function(options) {
			if (!options || !options.firstLoad) {
				this._reset();
			}
			this._findElements();
			this._findBackgrounds();
			// Fix for WebKit background rendering bug
			if (options && options.firstLoad && /WebKit/.test(navigator.userAgent)) {
				$(window).on('load', function() {
					var $el = $('body');
						oldLeft = $el.scrollLeft();
						oldTop = $el.scrollTop();

					$el.scrollLeft(oldLeft + 1);
					$el.scrollTop(oldTop + 1);

					$el.scrollLeft(oldLeft);
					$el.scrollTop(oldTop);
				});
			}
		},
		_detectViewport: function() {
			this.viewportWidth = this.$viewportElement.width();
			this.viewportHeight = this.$viewportElement.height();
			if (this.useSensor) {
				this.viewportFlipped = (window.orientation === 180);
				this.viewportLandscape = (Math.abs(window.orientation) === 90);
			}
		},
		_detectMobile: function() {
			// see http://detectmobilebrowser.com/mobile
			var browser = navigator.userAgent || navigator.vendor || window.opera;
			this.isMobile = (/(bb\d+|meego).+mobile|android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|playbook|plucker|pocket|psp|series(4|6)0|silk|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(browser)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(browser.substr(0,4)));
		},
		_detectMotionType: function() {
			this.useSensor = false;
			this.useSensorWebkit = false;
			this.useSensorMoz = false;
			this.useMouseMove = false;

			if (this.options.useGyroscope && (this.isMobile || this.options.inputPriority === 'gyroscope')) {
				// Webkit uses deviceorientation (DeviceOrientationEvent)
				this.useSensorWebkit = window.DeviceOrientationEvent !== undefined;
				// Mozilla uses MozOrientation (OrientationEvent)
				this.useSensorMoz = window.OrientationEvent !== undefined;
				this.useSensor = this.useSensorWebkit || this.useSensorMoz;
			}
			if (this.options.useMouseMove && !this.isMobile) {
				this.useMouseMove = this.$viewportElement.mousemove !== undefined;
			}
		},
		_findElements: function() {
			var self = this;

			if (this.elements !== undefined) {
				for (var i = this.elements.length - 1; i >= 0; i--) {
					this.elements[i].$element.data('parallaxify-ElementIsActive', undefined);
				}
			}

			this.elements = [];

			if(!this.options.parallaxElements) return;

			this.$element.find('[data-parallaxify-range],[data-parallaxify-range-x],[data-parallaxify-range-y]').each(function(i) {
				var $this = $(this);

				if (!$this.data('parallaxify-ElementIsActive')) {
					$this.data('parallaxify-ElementIsActive', this);
				} else if ($this.data('parallaxify-ElementIsActive') !== this) {
					return; // stop, because already set previously
				}

				// saving/restoring original positions
				if (!$this.data('parralaxify-originalLeft')) {
					$this.data('parallaxify-originalLeft', $this.css('left'));
					$this.data('parallaxify-originalTop', $this.css('top'));
				} else {
					$this.css('left', $this.data('parallaxify-originalLeft'));
					$this.css('top', $this.data('parallaxify-originalTop'));
				}

				// adding objects to element collection
				self.elements.push({
					$element: $this,
					originalPositionLeft: $this.position().left,
					originalPositionTop: $this.position().top,
					parallaxDistanceX: ($this.data('parallaxify-range-x') !== undefined ? $this.data('parallaxify-range-x') : ($this.data('parallaxify-range') !== undefined ? $this.data('parallaxify-range') : 0)),
					parallaxDistanceY: ($this.data('parallaxify-range-y') !== undefined ? $this.data('parallaxify-range-y') : ($this.data('parallaxify-range') !== undefined ? $this.data('parallaxify-range') : 0)),
					width: $this.outerWidth(true),
					height: $this.outerHeight(true)
				});
			});
		},
		_findBackgrounds: function() {
			var self = this,
				$backgroundElements;

			this.backgrounds = [];

			if(!this.options.parallaxBackgrounds) return;

			$backgroundElements = this.$element.find('[data-parallaxify-background-range],[data-parallaxify-background-range-x],[data-parallaxify-background-range-y]');

			if (this.$element.data('parallaxify-background-range') || this.$element.data('parallaxify-background-range-x') || this.$element.data('parallaxify-background-range-y')) {
				$backgroundElements = $backgroundElements.add(this.$element);
			}

			$backgroundElements.each(function() {
				var $this = $(this),
					backgroundPosition = getBackgroundPosition($this);

				if (!$this.data('parallaxify-backgroundIsActive')) {
					$this.data('parallaxify-backgroundIsActive', this);
				} else if ($this.data('parallaxify-backgroundIsActive') !== this) {
					return; // stop, because already set previously
				}

				// saving/restoring original background positions
				if (!$this.data('parralaxify-backgroundOriginalLeft')) {
					$this.data('parallaxify-backgroundOriginalLeft', backgroundPosition[0]);
					$this.data('parallaxify-backgroundOriginalTop', backgroundPosition[1]);
				} else {
					setBackgroundPosition($this, $this.data('parallaxify-backgroundOriginalLeft'), $this.data('parallaxify-backgroundOriginalTop'));
				}

				self.backgrounds.push({
					$element: $this,
					originalValueLeft: backgroundPosition[0],
					originalValueTop: backgroundPosition[1],
					originalBackgroundPositionLeft: (isNaN(parseInt(backgroundPosition[0], 10)) ? 0 : parseInt(backgroundPosition[0], 10)),
					originalBackgroundPositionTop: (isNaN(parseInt(backgroundPosition[1], 10)) ? 0 : parseInt(backgroundPosition[1], 10)),
					originalPositionLeft: $this.position().left,
					originalPositionTop: $this.position().top,
					parallaxDistanceX: ($this.data('parallaxify-background-range-x') !== undefined ? $this.data('parallaxify-background-range-x') : ($this.data('parallaxify-background-range') !== undefined ? $this.data('parallaxify-background-range') : 0)),
					parallaxDistanceY: ($this.data('parallaxify-background-range-y') !== undefined ? $this.data('parallaxify-background-range-y') : ($this.data('parallaxify-background-range') !== undefined ? $this.data('parallaxify-background-range') : 0))
				});
			});
		},
		_reset: function() {
			var element,
				originalPositionLeft,
				originalPositionTop,
				background,
				i;

			for (i = this.elements.length - 1; i >= 0; i--) {
				element = this.elements[i];
				originalPositionLeft = element.$element.data('parallaxify-originalLeft');
				originalPositionTop = element.$element.data('parallaxify-originalTop');

				this._setPosition(element.$element, originalPositionLeft, originalPositionLeft, originalPositionTop, originalPositionTop);

				element.$element.data('parallaxify-originalLeft', null).data('parallaxify-originalLeft', null).data('parallaxify-elementIsActive', null).data('parallaxify-backgroundIsActive', null);
			}

			for (i = this.backgrounds.length - 1; i >= 0; i--) {
				background = this.backgrounds[i];

				background.$element.data('parallaxify-backgroundOriginalLeft', null).data('parallaxify-backgroundOriginalTop', null).data('parallaxify-backgroundIsActive', null);

				setBackgroundPosition(background.$element, background.originalValueLeft, background.originalValueTop);
			}
		},
		destroy: function() {
			this._reset();

			if (this.useMouseMove) this.$viewportElement.unbind('mousemove.' + this.name);
			if (this.useSensorWebkit) window.removeEventListener('deviceorientation', this._handleSensorWebkit, false);
			if (this.useSensorMoz) window.removeEventListener('MozOrientation', this._handleSensorMoz, false);

			$(window).unbind('load.' + this.name).unbind('resize.' + this.name).unbind('orientationchange.' + this.name);
		},
		_processSensorData: function() {

			if (!this.useSensor) return;

			// beta is device pitch (moving up - down)
			// values are from -90 to 90
			// gamma is device roll (moving left right)
			// values are from -180 to 180

			var beta = this.beta,
				gamma = this.gamma,
				deltaBeta = 0,
				deltaGamma = 0;

			// counteract some bugs on Android where return values are 270 upon flipping the device
			if (beta > 90) beta = beta - 180;
			if (gamma > 180) gamma = gamma - 360;

			if (this.initialBeta === undefined && beta !== null) {
				this.initialBeta = beta;
				if (this.useSensor && this.options.inputPriority === 'gyroscope') {
					this.useMouseMove = false;
					if (this.useMouseMove) this.$viewportElement.unbind('mousemove.' + this.name);
				}
			}

			if (this.initialGamma === undefined && gamma !== null) {
				this.initialGamma = gamma;
				if (this.useSensor && this.options.inputPriority === 'gyroscope') {
					this.useMouseMove = false;
					if (this.useMouseMove) this.$viewportElement.unbind('mousemove.' + this.name);
				}
			}

			if (this.options.adjustBasePosition && this.initialGamma !== undefined && this.initialBeta !== undefined) {

				// adjust positions (accepting position out of range to smooth laying device upside down)
				if (gamma - this.initialGamma < -180) {
					this.initialGamma = lowPassFilter(gamma + 360, this.initialGamma, this.options.alphaPosition);
				} else if (gamma - this.initialGamma > 180) {
					this.initialGamma = lowPassFilter(gamma - 360, this.initialGamma, this.options.alphaPosition);
				} else {
					this.initialGamma = lowPassFilter(gamma, this.initialGamma, this.options.alphaPosition);
				}

				if (beta - this.initialBeta < -90) {
					this.initialBeta = lowPassFilter(beta + 180, this.initialBeta, this.options.alphaPosition);
				} else if (beta - this.initialBeta > 90) {
					this.initialBeta = lowPassFilter(beta - 180, this.initialBeta, this.options.alphaPosition);
				} else {
					this.initialBeta = lowPassFilter(beta, this.initialBeta, this.options.alphaPosition);
				}

			}

			deltaBeta = (this.initialBeta !== undefined ? beta - this.initialBeta : beta);
			deltaGamma = (this.initialGamma !== undefined ? gamma - this.initialGamma : gamma);

			if (deltaBeta > 100) {
				deltaBeta = deltaBeta - 180;
			} else if (deltaBeta < -100) {
				deltaBeta = deltaBeta + 180;
			}

			if (deltaGamma > 200) {
				deltaGamma = deltaGamma - 360;
			} else if (deltaGamma < -200) {
				deltaGamma = deltaGamma + 360;
			}

			// use low pass filter on signal
			deltaBeta = lowPassFilter(deltaBeta, this.tilt.beta, this.options.alphaFilter);
			deltaGamma = lowPassFilter(deltaGamma, this.tilt.gamma, this.options.alphaFilter);

			this.tilt.beta = deltaBeta;
			this.tilt.gamma = deltaGamma;

		},
		_repositionElements: function() {
			var moveHorizontal = this._getMoveHorizontal(),
				moveVertical = this._getMoveVertical(),
				element,
				background,
				bgLeft,
				bgTop,
				newPositionLeft,
				newPositionTop,
				i;

			if (this.currentMoveHorizontal === moveHorizontal && this.currentMoveVertical === moveVertical && this.currentWidth === this.viewportWidth && this.currentHeight === this.viewportHeight) {
				return;
			} else {
				this.currentMoveHorizontal = moveHorizontal;
				this.currentMoveVertical = moveVertical;
				this.currentWidth = this.viewportWidth;
				this.currentHeight = this.viewportHeight;
			}

			// Reposition elements
			for (i = this.elements.length - 1; i >= 0; i--) {
				element = this.elements[i];

				// New positions
				if (this.options.horizontalParallax) {
					newPositionLeft = Math.floor(moveHorizontal * element.parallaxDistanceX / 2) + element.originalPositionLeft;
				} else {
					newPositionLeft = element.originalPositionLeft;
				}

				if (this.options.verticalParallax) {
					newPositionTop = Math.floor(moveVertical * element.parallaxDistanceY / 2) + element.originalPositionTop;
				} else {
					newPositionTop = element.originalPositionTop;
				}

				this._setPosition(element.$element, newPositionLeft, element.originalPositionLeft, newPositionTop, element.originalPositionTop);

			}

			// Reposition backgrounds
			for (i = this.backgrounds.length - 1; i >= 0; i--) {
				background = this.backgrounds[i];

				bgLeft = (this.options.horizontalParallax ? Math.floor(moveHorizontal * background.parallaxDistanceX / 2)  + background.originalBackgroundPositionLeft + 'px' : background.originalValueLeft);
				bgTop = (this.options.verticalParallax ? Math.floor(moveVertical * background.parallaxDistanceY / 2)  + background.originalBackgroundPositionTop + 'px' : background.originalValueTop);

				setBackgroundPosition(background.$element, bgLeft, bgTop);
			}
		},
		_handleWindowLoadAndResize: function() {
			var self = this,
				$window = $(window);

			if (self.options.responsive) {
				$window.bind('load.' + this.name, function() {
					self.refresh();
				});
			}

			$window.bind('resize.' + this.name, function() {
				self._detectViewport();

				if (self.options.responsive) {
					self.refresh();
				}
			});

			$window.bind('orientationchange.' + this.name, function() {
				self._detectViewport();

				if (self.options.responsive) {
					self.refresh();
				}
			});
		},
		// self loading function to constantly reposition elements within each animation frame
		_startAnimation: function() {
			var self = this,
				ticking = false;

			this.beta = 0;
			this.gamma = 0;
			this.clientX = this.oldClientX = Math.round(self.viewportWidth / 2);
			this.clientY = this.oldClientY = Math.round(self.viewportHeight / 2); // */

			var update = function() {
				self._processSensorData();
				self._repositionElements();
				ticking = false;
			};

			var requestTick = function() {
				if (!ticking) {
					requestAnimationFrame(update);
					ticking = true;
				}
			};

			this._handleSensorWebkit = function(e) {
				// gamma is device roll (moving left right)
				// values are from -180 to 180
				self.gamma = e.gamma;

				// beta is device pitch (moving up - down)
				// values are from -90 to 90
				self.beta = e.beta;

				requestTick();
			};

			this._handleSensorMoz = function(e) {
				// x is device roll (moving left right)
				// values are from -1 to 1
				self.gamma = e.x * 180;

				// y is device pitch (moving up - down)
				// values are from +1 to -1
				self.beta = e.y * -90;

				requestTick();
			};

			this._handleMouseMove = function(e) {
				self.oldClientX = self.clientX;
				self.oldClientY = self.clientY;
				e.clientX !== undefined ? self.clientX = e.clientX : self.clientX = e.pageX;
				e.clientY !== undefined ? self.clientY = e.clientY : self.clientY = e.pageY;

				requestTick();
			};

			// bind sensor events to updates
			if (this.useSensorWebkit) {
				window.addEventListener('deviceorientation', self._handleSensorWebkit, false);
			} else if (this.useSensorMoz) {
				window.addEventListener('MozOrientation', self._handleSensorMoz, false);
			}
			// bind mouse move event
			if (this.useMouseMove) {
				this.$viewportElement.bind('mousemove.' + this.name, self._handleMouseMove);
			}

			requestTick();
		}
	};

	$.fn[pluginName] = function (options) {
		var args = arguments;
		if (options === undefined || typeof options === 'object') {
			return this.each(function () {
				if (!$.data(this, 'plugin_' + pluginName)) {
					$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
				}
			});
		} else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
			return this.each(function () {
				var instance = $.data(this, 'plugin_' + pluginName);
				if (instance instanceof Plugin && typeof instance[options] === 'function') {
					instance[options].apply(instance, Array.prototype.slice.call(args, 1));
				}
				if (options === 'destroy') {
					$.data(this, 'plugin_' + pluginName, null);
				}
			});
		}
	};

	$[pluginName] = function(options) {
		var $window = $(window);
		return $window[pluginName].apply($window, Array.prototype.slice.call(arguments, 0));
	};

	// Expose the position property function hashes so they can be extended
	$[pluginName].positionProperty = positionProperty;

	// Expose the motion type function hashes so they can be extended
	$[pluginName].motionType = motionType;

	// Expose the plugin class so it can be modified
	window[pluginName] = Plugin;
}(jQuery, this, document));
