/**
 * jmpress.js v0.4.5
 * http://jmpressjs.github.com/jmpress.js
 *
 * A jQuery plugin to build a website on the infinite canvas.
 *
 * Copyright 2013 Kyle Robinson Young @shama & Tobias Koppers @sokra
 * Licensed MIT
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Based on the foundation laid by Bartek Szopka @bartaz
 *//*
 * core.js
 * The core of jmpress.js
 */
(function( $, document, window, undefined ) {

	'use strict';

	/**
	 * Set supported prefixes
	 *
	 * @access protected
	 * @return Function to get prefixed property
	 */
	var pfx = (function () {
		var style = document.createElement('dummy').style,
			prefixes = 'Webkit Moz O ms Khtml'.split(' '),
			memory = {};
		return function ( prop ) {
			if ( typeof memory[ prop ] === "undefined" ) {
				var ucProp  = prop.charAt(0).toUpperCase() + prop.substr(1),
					props   = (prop + ' ' + prefixes.join(ucProp + ' ') + ucProp).split(' ');
				memory[ prop ] = null;
				for ( var i in props ) {
					if ( style[ props[i] ] !== undefined ) {
						memory[ prop ] = props[i];
						break;
					}
				}
			}
			return memory[ prop ];
		};
	}());

	/**
	 * map ex. "WebkitTransform" to "-webkit-transform"
	 */
	function mapProperty( name ) {
		if(!name) {
			return;
		}
		var index = 1 + name.substr(1).search(/[A-Z]/);
		var prefix = name.substr(0, index).toLowerCase();
		var postfix = name.substr(index).toLowerCase();
		return "-" + prefix + "-" + postfix;
	}
	function addComma( attribute ) {
		if(!attribute) {
			return "";
		}
		return attribute + ",";
	}
	/**
	 * Return an jquery object only if it's not empty
	 */
	function ifNotEmpty(el) {
		if(el.length > 0) {
			return el;
		}
		return null;
	}

	/**
	 * Default Settings
	 */
	var defaults = {
		/* CLASSES */
		stepSelector: '.step'
		,containerClass: ''
		,canvasClass: ''
		,areaClass: ''
		,notSupportedClass: 'not-supported'

		/* CONFIG */
		,fullscreen: true

		/* ANIMATION */
		,animation: {
			transformOrigin: 'top left'
			,transitionProperty: addComma(mapProperty(pfx('transform'))) + addComma(mapProperty(pfx('perspective'))) + 'opacity'
			,transitionDuration: '1s'
			,transitionDelay: '500ms'
			,transitionTimingFunction: 'ease-in-out'
			,transformStyle: "preserve-3d"
		}
		,transitionDuration: 1500
	};
	var callbacks = {
		'beforeChange': 1
		,'beforeInitStep': 1
		,'initStep': 1
		,'beforeInit': 1
		,'afterInit': 1
		,'beforeDeinit': 1
		,'afterDeinit': 1
		,'applyStep': 1
		,'unapplyStep': 1
		,'setInactive': 1
		,'beforeActive': 1
		,'setActive': 1
		,'selectInitialStep': 1
		,'selectPrev': 1
		,'selectNext': 1
		,'selectHome': 1
		,'selectEnd': 1
		,'idle': 1
		,'applyTarget': 1
	};
	for(var callbackName in callbacks) {
		defaults[callbackName] = [];
	}


	/**
	 * Initialize jmpress
	 */
	function init( args ) {
		args = $.extend(true, {}, args || {});

		// accept functions and arrays of functions as callbacks
		var callbackArgs = {};
		var callbackName = null;
		for (callbackName in callbacks) {
			callbackArgs[callbackName] = $.isFunction( args[callbackName] ) ?
				[ args[callbackName] ] :
				args[callbackName];
			args[callbackName] = [];
		}

		// MERGE SETTINGS
		var settings = $.extend(true, {}, defaults, args);

		for (callbackName in callbacks) {
			if (callbackArgs[callbackName]) {
				Array.prototype.push.apply(settings[callbackName], callbackArgs[callbackName]);
			}
		}

		/*** MEMBER VARS ***/

		var jmpress = $( this )
			,container = null
			,area = null
			,oldStyle = {
				container: ""
				,area: ""
			}
			,canvas = null
			,current = null
			,active = false
			,activeSubstep = null
			,activeDelegated = false;


		/*** MEMBER FUNCTIONS ***/
		// functions have to be called with this

		/**
		 * Init a single step
		 *
		 * @param element the element of the step
		 * @param idx number of step
		 */
		function doStepInit( element, idx ) {
			var data = dataset( element );
			var step = {
				oldStyle: $(element).attr("style") || ""
			};

			var callbackData = {
				data: data
				,stepData: step
			};
			callCallback.call(this, 'beforeInitStep', $(element), callbackData);
			step.delegate = data.delegate;
			callCallback.call(this, 'initStep', $(element), callbackData);

			$(element).data('stepData', step);

			if ( !$(element).attr('id') ) {
				$(element).attr('id', 'step-' + (idx + 1));
			}

			callCallback.call(this, 'applyStep', $(element), callbackData);
		}
		/**
		 * Deinit a single step
		 *
		 * @param element the element of the step
		 */
		function doStepDeinit( element ) {
			var stepData = $(element).data('stepData');

			$(element).attr("style", stepData.oldStyle);

			callCallback.call(this, 'unapplyStep', $(element), {
				stepData: stepData
			});
		}
		/**
		 * Reapplies stepData to the element
		 *
		 * @param element
		 */
		function doStepReapply( element ) {
			callCallback.call(this, 'unapplyStep', $(element), {
				stepData: element.data("stepData")
			});

			callCallback.call(this, 'applyStep', $(element), {
				stepData: element.data("stepData")
			});
		}
		/**
		 * Completly deinit jmpress
		 *
		 */
		function deinit() {
			if ( active ) {
				callCallback.call(this, 'setInactive', active, {
					stepData: $(active).data('stepData')
					,reason: "deinit"
				} );
			}
			if (current.jmpressClass) {
				$(jmpress).removeClass(current.jmpressClass);
			}

			callCallback.call(this, 'beforeDeinit', $(this), {});

			$(settings.stepSelector, jmpress).each(function( idx ) {
				doStepDeinit.call(jmpress, this );
			});

			container.attr("style", oldStyle.container);
			if(settings.fullscreen) {
				$("html").attr("style", "");
			}
			area.attr("style", oldStyle.area);
			$(canvas).children().each(function() {
				jmpress.append( $( this ) );
			});
			if( settings.fullscreen ) {
				canvas.remove();
			} else {
				canvas.remove();
				area.remove();
			}

			callCallback.call(this, 'afterDeinit', $(this), {});

			$(jmpress).data("jmpressmethods", false);
		}
		/**
		 * Call a callback
		 *
		 * @param callbackName String callback which should be called
		 * @param element some arguments to the callback
		 * @param eventData
		 */
		function callCallback( callbackName, element, eventData ) {
			eventData.settings = settings;
			eventData.current = current;
			eventData.container = container;
			eventData.parents = element ? getStepParents(element) : null;
			eventData.current = current;
			eventData.jmpress = this;
			var result = {};
			$.each( settings[callbackName], function(idx, callback) {
				result.value = callback.call( jmpress, element, eventData ) || result.value;
			});
			return result.value;
		}
		/**
		 *
		 */
		function getStepParents( el ) {
			return $(el).parentsUntil(jmpress).not(jmpress).filter(settings.stepSelector);
		}
		/**
		 * Reselect the active step
		 *
		 * @param String type reason of reselecting step
		 */
		function reselect( type ) {
			return select( { step: active, substep: activeSubstep }, type);
		}
		/**
		 * Select a given step
		 *
		 * @param el element to select
		 * @param type reason of changing step
		 * @return Object element selected
		 */
		function select( el, type ) {
			var substep;
			if ( $.isPlainObject( el ) ) {
				substep = el.substep;
				el = el.step;
			}
			if ( typeof el === 'string') {
				el = jmpress.find( el ).first();
			}
			if ( !el || !$(el).data('stepData') ) {
				return false;
			}

			scrollFix.call(this);

			var step = $(el).data('stepData');

			var cancelSelect = false;
			callCallback.call(this, "beforeChange", el, {
				stepData: step
				,reason: type
				,cancel: function() {
					cancelSelect = true;
				}
			});
			if (cancelSelect) {
				return undefined;
			}

			var target = {};

			var delegated = el;
			if($(el).data("stepData").delegate) {
				delegated = ifNotEmpty($(el).parentsUntil(jmpress).filter(settings.stepSelector).filter(step.delegate)) ||
					ifNotEmpty($(el).near(step.delegate)) ||
					ifNotEmpty($(el).near(step.delegate, true)) ||
					ifNotEmpty($(step.delegate, jmpress));
				if(delegated) {
					step = delegated.data("stepData");
				} else {
					// Do not delegate if expression not found
					delegated = el;
				}
			}
			if ( activeDelegated ) {
				callCallback.call(this, 'setInactive', activeDelegated, {
					stepData: $(activeDelegated).data('stepData')
					,delegatedFrom: active
					,reason: type
					,target: target
					,nextStep: delegated
					,nextSubstep: substep
					,nextStepData: step
				} );
			}
			var callbackData = {
				stepData: step
				,delegatedFrom: el
				,reason: type
				,target: target
				,substep: substep
				,prevStep: activeDelegated
				,prevSubstep: activeSubstep
				,prevStepData: activeDelegated && $(activeDelegated).data('stepData')
			};
			callCallback.call(this, 'beforeActive', delegated, callbackData);
			callCallback.call(this, 'setActive', delegated, callbackData);

			// Set on step class on root element
			if (current.jmpressClass) {
				$(jmpress).removeClass(current.jmpressClass);
			}
			$(jmpress).addClass(current.jmpressClass = 'step-' + $(delegated).attr('id') );
			if (current.jmpressDelegatedClass) {
				$(jmpress).removeClass(current.jmpressDelegatedClass);
			}
			$(jmpress).addClass(current.jmpressDelegatedClass = 'delegating-step-' + $(el).attr('id') );

			callCallback.call(this, "applyTarget", delegated, $.extend({
				canvas: canvas
				,area: area
				,beforeActive: activeDelegated
			}, callbackData));

			active = el;
			activeSubstep = callbackData.substep;
			activeDelegated = delegated;

			if(current.idleTimeout) {
				clearTimeout(current.idleTimeout);
			}
			current.idleTimeout = setTimeout(function() {
				callCallback.call(this, 'idle', delegated, callbackData);
			}, Math.max(1, settings.transitionDuration - 100));

			return delegated;
		}
		/**
		 * This should fix ANY kind of buggy scrolling
		 */
		function scrollFix() {
			(function fix() {
				if ($(container)[0].tagName === "BODY") {
					try {
						window.scrollTo(0, 0);
					} catch(e) {}
				}
				$(container).scrollTop(0);
				$(container).scrollLeft(0);
				function check() {
					if ($(container).scrollTop() !== 0 ||
						$(container).scrollLeft() !== 0) {
							fix();
						}
				}
				setTimeout(check, 1);
				setTimeout(check, 10);
				setTimeout(check, 100);
				setTimeout(check, 200);
				setTimeout(check, 400);
			}());
		}
		/**
		 * Alias for select
		 */
		function goTo( el ) {
			return select.call(this, el, "jump" );
		}
		/**
		 * Goto Next Slide
		 *
		 * @return Object newly active slide
		 */
		function next() {
			return select.call(this, callCallback.call(this, 'selectNext', active, {
				stepData: $(active).data('stepData')
				,substep: activeSubstep
			}), "next" );
		}
		/**
		 * Goto Previous Slide
		 *
		 * @return Object newly active slide
		 */
		function prev() {
			return select.call(this, callCallback.call(this, 'selectPrev', active, {
				stepData: $(active).data('stepData')
				,substep: activeSubstep
			}), "prev" );
		}
		/**
		 * Goto First Slide
		 *
		 * @return Object newly active slide
		 */
		function home() {
			return select.call(this, callCallback.call(this, 'selectHome', active, {
				stepData: $(active).data('stepData')
			}), "home" );
		}
		/**
		 * Goto Last Slide
		 *
		 * @return Object newly active slide
		 */
		function end() {
			return select.call(this,   callCallback.call(this, 'selectEnd', active, {
				stepData: $(active).data('stepData')
			}), "end" );
		}
		/**
		 * Manipulate the canvas
		 *
		 * @param props
		 * @return Object
		 */
		function canvasMod( props ) {
			css(canvas, props || {});
			return $(canvas);
		}
		/**
		 * Return current step
		 *
		 * @return Object
		 */
		function getActive() {
			return activeDelegated && $(activeDelegated);
		}
		/**
		 * fire a callback
		 *
		 * @param callbackName
		 * @param element
		 * @param eventData
		 * @return void
		 */
		function fire( callbackName, element, eventData ) {
			if( !callbacks[callbackName] ) {
				$.error( "callback " + callbackName + " is not registered." );
			} else {
				return callCallback.call(this, callbackName, element, eventData);
			}
		}

		/**
		 * PUBLIC METHODS LIST
		 */
		jmpress.data("jmpressmethods", {
			select: select
			,reselect: reselect
			,scrollFix: scrollFix
			,goTo: goTo
			,next: next
			,prev: prev
			,home: home
			,end: end
			,canvas: canvasMod
			,container: function() { return container; }
			,settings: function() { return settings; }
			,active: getActive
			,current: function() { return current; }
			,fire: fire
			,init: function(step) {
				doStepInit.call(this, $(step), current.nextIdNumber++);
			}
			,deinit: function(step) {
				if(step) {
					doStepDeinit.call(this, $(step));
				} else {
					deinit.call(this);
				}
			}
			,reapply: doStepReapply
		});

		/**
		 * Check for support
		 * This will be removed in near future, when support is coming
		 *
		 * @access protected
		 * @return void
		 */
		function checkSupport() {
			var ua = navigator.userAgent.toLowerCase();
			return (ua.search(/(iphone)|(ipod)|(android)/) === -1) || (ua.search(/(chrome)/) !== -1);
		}

		// BEGIN INIT

		// CHECK FOR SUPPORT
		if (checkSupport() === false) {
			if (settings.notSupportedClass) {
				jmpress.addClass(settings.notSupportedClass);
			}
			return;
		} else {
			if (settings.notSupportedClass) {
				jmpress.removeClass(settings.notSupportedClass);
			}
		}

		// grabbing all steps
		var steps = $(settings.stepSelector, jmpress);

		// GERNERAL INIT OF FRAME
		container = jmpress;
		area = $('<div />');
		canvas = $('<div />');
		$(jmpress).children().filter(steps).each(function() {
			canvas.append( $( this ) );
		});
		if(settings.fullscreen) {
			container = $('body');
			$("html").css({
				overflow: 'hidden'
			});
			area = jmpress;
		}
		oldStyle.area = area.attr("style") || "";
		oldStyle.container = container.attr("style") || "";
		if(settings.fullscreen) {
			container.css({
				height: '100%'
			});
			jmpress.append( canvas );
		} else {
			container.css({
				position: "relative"
			});
			area.append( canvas );
			jmpress.append( area );
		}

		$(container).addClass(settings.containerClass);
		$(area).addClass(settings.areaClass);
		$(canvas).addClass(settings.canvasClass);

		document.documentElement.style.height = "100%";
		container.css({
			overflow: 'hidden'
		});

		var props = {
			position: "absolute"
			,transitionDuration: '0s'
		};
		props = $.extend({}, settings.animation, props);
		css(area, props);
		css(area, {
			top: '50%'
			,left: '50%'
			,perspective: '1000px'
		});
		css(canvas, props);

		current = {};

		callCallback.call(this, 'beforeInit', null, {});

		// INITIALIZE EACH STEP
		steps.each(function( idx ) {
			doStepInit.call(jmpress, this, idx );
		});
		current.nextIdNumber = steps.length;

		callCallback.call(this, 'afterInit', null, {});

		// START
		select.call(this,  callCallback.call(this, 'selectInitialStep', "init", {}) );

		if (settings.initClass) {
			$(steps).removeClass(settings.initClass);
		}
	}
	/**
	 * Return default settings
	 *
	 * @return Object
	 */
	function getDefaults() {
		return defaults;
	}
	/**
	 * Register a callback or a jmpress function
	 *
	 * @access public
	 * @param name String the name of the callback or function
	 * @param func Function? the function to be added
	 */
	function register(name, func) {
		if( $.isFunction(func) ) {
			if( methods[name] ) {
				$.error( "function " + name + " is already registered." );
			} else {
				methods[name] = func;
			}
		} else {
			if( callbacks[name] ) {
				$.error( "callback " + name + " is already registered." );
			} else {
				callbacks[name] = 1;
				defaults[name] = [];
			}
		}
	}
	/**
	 * Set CSS on element w/ prefixes
	 *
	 * @return Object element which properties were set
	 *
	 * TODO: Consider bypassing pfx and blindly set as jQuery
	 * already checks for support
	 */
	function css( el, props ) {
		var key, pkey, cssObj = {};
		for ( key in props ) {
			if ( props.hasOwnProperty(key) ) {
				pkey = pfx(key);
				if ( pkey !== null ) {
					cssObj[pkey] = props[key];
				}
			}
		}
		$(el).css(cssObj);
		return el;
	}
	/**
	 * Return dataset for element
	 *
	 * @param el element
	 * @return Object
	 */
	function dataset( el ) {
		if ( $(el)[0].dataset ) {
			return $.extend({}, $(el)[0].dataset);
		}
		function toCamelcase( str ) {
			str = str.split( '-' );
			for( var i = 1; i < str.length; i++ ) {
				str[i] = str[i].substr(0, 1).toUpperCase() + str[i].substr(1);
			}
			return str.join( '' );
		}
		var returnDataset = {};
		var attrs = $(el)[0].attributes;
		$.each(attrs, function ( idx, attr ) {
			if ( attr.nodeName.substr(0, 5) === "data-" ) {
				returnDataset[ toCamelcase(attr.nodeName.substr(5)) ] = attr.nodeValue;
			}
		});
		return returnDataset;
	}
	/**
	 * Returns true, if jmpress is initialized
	 *
	 * @return bool
	 */
	function initialized() {
		return !!$(this).data("jmpressmethods");
	}


	/**
	 * PUBLIC STATIC METHODS LIST
	 */
	var methods = {
		init: init
		,initialized: initialized
		,deinit: function() {}
		,css: css
		,pfx: pfx
		,defaults: getDefaults
		,register: register
		,dataset: dataset
	};

	/**
	 * $.jmpress()
	 */
	$.fn.jmpress = function( method ) {
		function f() {
			var jmpressmethods = $(this).data("jmpressmethods");
			if ( jmpressmethods && jmpressmethods[method] ) {
				return jmpressmethods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
			} else if ( methods[method] ) {
				return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
			} else if ( callbacks[method] && jmpressmethods ) {
				var settings = jmpressmethods.settings();
				var func = Array.prototype.slice.call( arguments, 1 )[0];
				if ($.isFunction( func )) {
					settings[method] = settings[method] || [];
					settings[method].push(func);
				}
			} else if ( typeof method === 'object' || ! method ) {
				return init.apply( this, arguments );
			} else {
				$.error( 'Method ' +  method + ' does not exist on jQuery.jmpress' );
			}
			// to allow chaining
			return this;
		}
		var args = arguments;
		var result;
		$(this).each(function(idx, element) {
			result = f.apply(element, args);
		});
		return result;
	};
	$.extend({
		jmpress: function( method ) {
			if ( methods[method] ) {
				return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
			} else if ( callbacks[method] ) {
				// plugin interface
				var func = Array.prototype.slice.call( arguments, 1 )[0];
				if ($.isFunction( func )) {
					defaults[method].push(func);
				} else {
					$.error( 'Second parameter should be a function: $.jmpress( callbackName, callbackFunction )' );
				}
			} else {
				$.error( 'Method ' +  method + ' does not exist on jQuery.jmpress' );
			}
		}
	});

}(jQuery, document, window));

/*
 * near.js
 * Find steps near each other
 */
(function( $, document, window, undefined ) {

	'use strict';

	// add near( selector, backwards = false) to jquery


	function checkAndGo( elements, func, selector, backwards ) {
		var next;
		elements.each(function(idx, element) {
			if(backwards) {
				next = func(element, selector, backwards);
				if (next) {
					return false;
				}
			}
			if( $(element).is(selector) ) {
				next = element;
				return false;
			}
			if(!backwards) {
				next = func(element, selector, backwards);
				if (next) {
					return false;
				}
			}
		});
		return next;
	}
	function findNextInChildren(item, selector, backwards) {
		var children = $(item).children();
		if(backwards) {
			children = $(children.get().reverse());
		}
		return checkAndGo( children, findNextInChildren, selector, backwards );
	}
	function findNextInSiblings(item, selector, backwards) {
		return checkAndGo(
			$(item)[backwards ? "prevAll" : "nextAll"](),
			findNextInChildren, selector, backwards );
	}
	function findNextInParents(item, selector, backwards) {
		var next;
		var parents = $(item).parents();
		parents = $(parents.get());
		$.each(parents.get(), function(idx, element) {
			if( backwards && $(element).is(selector) ) {
				next = element;
				return false;
			}
			next = findNextInSiblings(element, selector, backwards);
			if(next) {
				return false;
			}
		});
		return next;
	}

	$.fn.near = function( selector, backwards ) {
		var array = [];
		$(this).each(function(idx, element) {
			var near = (backwards ?
					false :
					findNextInChildren( element, selector, backwards )) ||
				findNextInSiblings( element, selector, backwards ) ||
				findNextInParents( element, selector, backwards );
			if( near ) {
				array.push(near);
			}
		});
		return $(array);
	};
}(jQuery, document, window));
/*
 * transform.js
 * The engine that powers the transforms or falls back to other methods
 */
(function( $, document, window, undefined ) {

	'use strict';

	/* FUNCTIONS */
	function toCssNumber(number) {
		return (Math.round(10000*number)/10000)+"";
	}

	/**
	 * 3D and 2D engines
	 */
	var engines = {
		3: {
			transform: function( el, data ) {
				var transform = 'translate(-50%,-50%)';
				$.each(data, function(idx, item) {
					var coord = ["X", "Y", "Z"];
					var i;
					if(item[0] === "translate") { // ["translate", x, y, z]
						transform += " translate3d(" + toCssNumber(item[1] || 0) + "px," + toCssNumber(item[2] || 0) + "px," + toCssNumber(item[3] || 0) + "px)";
					} else if(item[0] === "rotate") {
						var order = item[4] ? [1, 2, 3] : [3, 2, 1];
						for(i = 0; i < 3; i++) {
							transform += " rotate" + coord[order[i]-1] + "(" + toCssNumber(item[order[i]] || 0) + "deg)";
						}
					} else if(item[0] === "scale") {
						for(i = 0; i < 3; i++) {
							transform += " scale" + coord[i] + "(" + toCssNumber(item[i+1] || 1) + ")";
						}
					}
				});
				$.jmpress("css", el, $.extend({}, { transform: transform }));
			}
		}
		,2: {
			transform: function( el, data ) {
				var transform = 'translate(-50%,-50%)';
				$.each(data, function(idx, item) {
					var coord = ["X", "Y"];
					if(item[0] === "translate") { // ["translate", x, y, z]
						transform += " translate(" + toCssNumber(item[1] || 0) + "px," + toCssNumber(item[2] || 0) + "px)";
					} else if(item[0] === "rotate") {
						transform += " rotate(" + toCssNumber(item[3] || 0) + "deg)";
					} else if(item[0] === "scale") {
						for(var i = 0; i < 2; i++) {
							transform += " scale" + coord[i] + "(" + toCssNumber(item[i+1] || 1) + ")";
						}
					}
				});
				$.jmpress("css", el, $.extend({}, { transform: transform }));
			}
		}
		,1: {
			// CHECK IF SUPPORT IS REALLY NEEDED?
			// this not even work without scaling...
			// it may better to display the normal view
			transform: function( el, data ) {
				var anitarget = { top: 0, left: 0 };
				$.each(data, function(idx, item) {
					var coord = ["X", "Y"];
					if(item[0] === "translate") { // ["translate", x, y, z]
						anitarget.left = Math.round(item[1] || 0) + "px";
						anitarget.top = Math.round(item[2] || 0) + "px";
					}
				});
				el.animate(anitarget, 1000); // TODO: Use animation duration
			}
		}
	};

	/**
	 * Engine to power cross-browser translate, scale and rotate.
	 */
	var engine = (function() {
		if ($.jmpress("pfx", "perspective")) {
			return engines[3];
		} else if ($.jmpress("pfx", "transform")) {
			return engines[2];
		} else {
			// CHECK IF SUPPORT IS REALLY NEEDED?
			return engines[1];
		}
	}());

	$.jmpress("defaults").reasonableAnimation = {};
	$.jmpress("initStep", function( step, eventData ) {
		var data = eventData.data;
		var stepData = eventData.stepData;
		var pf = parseFloat;
		$.extend(stepData, {
			x: pf(data.x) || 0
			,y: pf(data.y) || 0
			,z: pf(data.z) || 0
			,r: pf(data.r) || 0
			,phi: pf(data.phi) || 0
			,rotate: pf(data.rotate) || 0
			,rotateX: pf(data.rotateX) || 0
			,rotateY: pf(data.rotateY) || 0
			,rotateZ: pf(data.rotateZ) || 0
			,revertRotate: false
			,scale: pf(data.scale) || 1
			,scaleX: pf(data.scaleX) || false
			,scaleY: pf(data.scaleY) || false
			,scaleZ: pf(data.scaleZ) || 1
		});
	});
	$.jmpress("afterInit", function( nil, eventData ) {
		var stepSelector = eventData.settings.stepSelector,
			current = eventData.current;
		current.perspectiveScale = 1;
		current.maxNestedDepth = 0;
		var nestedSteps = $(eventData.jmpress).find(stepSelector).children(stepSelector);
		while(nestedSteps.length) {
			current.maxNestedDepth++;
			nestedSteps = nestedSteps.children(stepSelector);
		}
	});
	$.jmpress("applyStep", function( step, eventData ) {
		$.jmpress("css", $(step), {
			position: "absolute"
			,transformStyle: "preserve-3d"
		});
		if ( eventData.parents.length > 0 ) {
			$.jmpress("css", $(step), {
				top: "50%"
				,left: "50%"
			});
		}
		var sd = eventData.stepData;
		var transform = [
			["translate",
				sd.x || (sd.r * Math.sin(sd.phi*Math.PI/180)),
				sd.y || (-sd.r * Math.cos(sd.phi*Math.PI/180)),
				sd.z],
			["rotate",
				sd.rotateX,
				sd.rotateY,
				sd.rotateZ || sd.rotate,
				true],
			["scale",
				sd.scaleX || sd.scale,
				sd.scaleY || sd.scale,
				sd.scaleZ || sd.scale]
		];
		engine.transform( step, transform );
	});
	$.jmpress("setActive", function( element, eventData ) {
		var target = eventData.target;
		var step = eventData.stepData;
		var tf = target.transform = [];
		target.perspectiveScale = 1;

		for(var i = eventData.current.maxNestedDepth; i > (eventData.parents.length || 0); i--) {
			tf.push(["scale"], ["rotate"], ["translate"]);
		}

		tf.push(["scale",
			1 / (step.scaleX || step.scale),
			1 / (step.scaleY || step.scale),
			1 / (step.scaleZ)]);
		tf.push(["rotate",
			-step.rotateX,
			-step.rotateY,
			-(step.rotateZ || step.rotate)]);
		tf.push(["translate",
			-(step.x || (step.r * Math.sin(step.phi*Math.PI/180))),
			-(step.y || (-step.r * Math.cos(step.phi*Math.PI/180))),
			-step.z]);
		target.perspectiveScale *= (step.scaleX || step.scale);

		$.each(eventData.parents, function(idx, element) {
			var step = $(element).data("stepData");
			tf.push(["scale",
				1 / (step.scaleX || step.scale),
				1 / (step.scaleY || step.scale),
				1 / (step.scaleZ)]);
			tf.push(["rotate",
				-step.rotateX,
				-step.rotateY,
				-(step.rotateZ || step.rotate)]);
			tf.push(["translate",
				-(step.x || (step.r * Math.sin(step.phi*Math.PI/180))),
				-(step.y || (-step.r * Math.cos(step.phi*Math.PI/180))),
				-step.z]);
			target.perspectiveScale *= (step.scaleX || step.scale);
		});

		$.each(tf, function(idx, item) {
			if(item[0] !== "rotate") {
				return;
			}
			function lowRotate(name) {
				if(eventData.current["rotate"+name+"-"+idx] === undefined) {
					eventData.current["rotate"+name+"-"+idx] = item[name] || 0;
				}
				var cur = eventData.current["rotate"+name+"-"+idx], tar = item[name] || 0,
					curmod = cur % 360, tarmod = tar % 360;
				if(curmod < 0) {
					curmod += 360;
				}
				if(tarmod < 0) {
					tarmod += 360;
				}
				var diff = tarmod - curmod;
				if(diff < -180) {
					diff += 360;
				} else if(diff > 180) {
					diff -= 360;
				}
				eventData.current["rotate"+name+"-"+idx] = item[name] = cur + diff;
			}
			lowRotate(1);
			lowRotate(2);
			lowRotate(3);
		});
	});
	$.jmpress("applyTarget", function( active, eventData ) {

		var target = eventData.target,
			props, step = eventData.stepData,
			settings = eventData.settings,
			zoomin = target.perspectiveScale * 1.3 < eventData.current.perspectiveScale,
			zoomout = target.perspectiveScale > eventData.current.perspectiveScale * 1.3;

		// extract first scale from transform
		var lastScale = -1;
		$.each(target.transform, function(idx, item) {
			if(item.length <= 1) {
				return;
			}
			if(item[0] === "rotate" &&
				item[1] % 360 === 0  &&
				item[2] % 360 === 0  &&
				item[3] % 360 === 0) {
				return;
			}
			if(item[0] === "scale") {
				lastScale = idx;
			} else {
				return false;
			}
		});

		if(lastScale !== eventData.current.oldLastScale) {
			zoomin = zoomout = false;
			eventData.current.oldLastScale = lastScale;
		}

		var extracted = [];
		if(lastScale !== -1) {
			while(lastScale >= 0) {
				if(target.transform[lastScale][0] === "scale") {
					extracted.push(target.transform[lastScale]);
					target.transform[lastScale] = ["scale"];
				}
				lastScale--;
			}
		}

		var animation = settings.animation;
		if(settings.reasonableAnimation[eventData.reason]) {
			animation = $.extend({},
				animation,
				settings.reasonableAnimation[eventData.reason]);
		}

		props = {
			// to keep the perspective look similar for different scales
			// we need to 'scale' the perspective, too
			perspective: Math.round(target.perspectiveScale * 1000) + "px"
		};
		props = $.extend({}, animation, props);
		if (!zoomin) {
			props.transitionDelay = '0s';
		}
		if (!eventData.beforeActive) {
			props.transitionDuration = '0s';
			props.transitionDelay = '0s';
		}
		$.jmpress("css", eventData.area, props);
		engine.transform(eventData.area, extracted);

		props = $.extend({}, animation);
		if (!zoomout) {
			props.transitionDelay = '0s';
		}
		if (!eventData.beforeActive) {
			props.transitionDuration = '0s';
			props.transitionDelay = '0s';
		}

		eventData.current.perspectiveScale = target.perspectiveScale;

		$.jmpress("css", eventData.canvas, props);
		engine.transform(eventData.canvas, target.transform);
	});

}(jQuery, document, window));
/*
 * active.js
 * Set the active classes on steps
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress;

	/* DEFINES */
	var activeClass = 'activeClass',
		nestedActiveClass = 'nestedActiveClass';

	/* DEFAULTS */
	var defaults = $jmpress( 'defaults' );
	defaults[nestedActiveClass] = "nested-active";
	defaults[activeClass]       = "active";

	/* HOOKS */
	$jmpress( 'setInactive', function( step, eventData ) {
		var settings = eventData.settings,
			activeClassSetting = settings[activeClass],
			nestedActiveClassSettings = settings[nestedActiveClass];
		if(activeClassSetting) {
			$(step).removeClass( activeClassSetting );
		}
		if(nestedActiveClassSettings) {
			$.each(eventData.parents, function(idx, element) {
				$(element).removeClass(nestedActiveClassSettings);
			});
		}
	});
	$jmpress( 'setActive', function( step, eventData ) {
		var settings = eventData.settings,
			activeClassSetting = settings[activeClass],
			nestedActiveClassSettings = settings[nestedActiveClass];
		if(activeClassSetting) {
			$(step).addClass( activeClassSetting );
		}
		if(nestedActiveClassSettings) {
			$.each(eventData.parents, function(idx, element) {
				$(element).addClass(nestedActiveClassSettings);
			});
		}
	});

}(jQuery, document, window));
/*
 * circular.js
 * Repeat from start after end
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress;

	/* FUNCTIONS */
	function firstSlide( step, eventData ) {
		return $(this).find(eventData.settings.stepSelector).first();
	}
	function prevOrNext( jmpress, step, eventData, prev) {
		if (!step) {
			return false;
		}
		var stepSelector = eventData.settings.stepSelector;
		step = $(step);
		do {
			var item = step.near( stepSelector, prev );
			if (item.length === 0 || item.closest(jmpress).length === 0) {
				item = $(jmpress).find(stepSelector)[prev?"last":"first"]();
			}
			if (!item.length) {
				return false;
			}
			step = item;
		} while( step.data("stepData").exclude );
		return step;
	}

	/* HOOKS */
	$jmpress( 'initStep', function( step, eventData ) {
		eventData.stepData.exclude = eventData.data.exclude && ["false", "no"].indexOf(eventData.data.exclude) === -1;
	});
	$jmpress( 'selectInitialStep', firstSlide);
	$jmpress( 'selectHome', firstSlide);
	$jmpress( 'selectEnd', function( step, eventData ) {
		return $(this).find(eventData.settings.stepSelector).last();
	});
	$jmpress( 'selectPrev', function( step, eventData ) {
		return prevOrNext(this, step, eventData, true);
	});
	$jmpress( 'selectNext', function( step, eventData ) {
		return prevOrNext(this, step, eventData);
	});
}(jQuery, document, window));
/*
 * start.js
 * Set the first step to start on
 */
(function( $, document, window, undefined ) {

	'use strict';

	/* HOOKS */
	$.jmpress( 'selectInitialStep', function( nil, eventData ) {
		return eventData.settings.start;
	});

}(jQuery, document, window));
/*
 * ways.js
 * Control the flow of the steps
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress;

	/* FUNCTIONS */
	function routeFunc( jmpress, route, type ) {
		for(var i = 0; i < route.length - 1; i++) {
			var from = route[i];
			var to = route[i+1];
			if($(jmpress).jmpress("initialized")) {
				$(from, jmpress).data("stepData")[type] = to;
			} else {
				$(from, jmpress).attr('data-' + type, to);
			}
		}
	}
	function selectPrevOrNext( step, eventData, attr, prev ) {
		var stepData = eventData.stepData;
		if(stepData[attr]) {
			var near = $(step).near(stepData[attr], prev);
			if(near && near.length) {
				return near;
			}
			near = $(stepData[attr], this)[prev?"last":"first"]();
			if(near && near.length) {
				return near;
			}
		}
	}

	/* EXPORTED FUNCTIONS */
	$jmpress( 'register', 'route', function( route, unidirectional, reversedRoute ) {
		if( typeof route === "string" ) {
			route = [route, route];
		}
		routeFunc(this, route, reversedRoute ? "prev" : "next");
		if (!unidirectional) {
			routeFunc(this, route.reverse(), reversedRoute ? "next" : "prev");
		}
	});

	/* HOOKS */
	$jmpress( 'initStep', function( step, eventData ) {
		for(var attr in {next:1,prev:1}) {
			eventData.stepData[attr] = eventData.data[attr];
		}
	});
	$jmpress( 'selectNext', function( step, eventData ) {
		return selectPrevOrNext.call(this, step, eventData, "next");
	});
	$jmpress( 'selectPrev', function( step, eventData ) {
		return selectPrevOrNext.call(this, step, eventData, "prev", true);
	});

}(jQuery, document, window));
/*
 * ajax.js
 * Load steps via ajax
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress;

	/* DEFINES */
	var afterStepLoaded = 'ajax:afterStepLoaded',
		loadStep = 'ajax:loadStep';

	/* REGISTER EVENTS */
	$jmpress('register', loadStep);
	$jmpress('register', afterStepLoaded);

	/* DEFAULTS */
	$jmpress('defaults').ajaxLoadedClass = "loaded";

	/* HOOKS */
	$jmpress('initStep', function( step, eventData ) {
		eventData.stepData.src = $(step).attr('href') || eventData.data.src || false;
		eventData.stepData.srcLoaded = false;
	});
	$jmpress(loadStep, function( step, eventData ) {
		var stepData = eventData.stepData,
			href = stepData && stepData.src,
			settings = eventData.settings;
		if ( href ) {
			$(step).addClass( settings.ajaxLoadedClass );
			stepData.srcLoaded = true;
			$(step).load(href, function(response, status, xhr) {
				$(eventData.jmpress).jmpress('fire', afterStepLoaded, step, $.extend({}, eventData, {
					response: response
					,status: status
					,xhr: xhr
				}));
			});
		}
	});
	$jmpress('idle', function( step, eventData ) {
		if (!step) {
			return;
		}
		var settings = eventData.settings,
			jmpress = $(this),
			stepData = eventData.stepData;
		var siblings = $(step)
			.add( $(step).near( settings.stepSelector ) )
			.add( $(step).near( settings.stepSelector, true) )
			.add( jmpress.jmpress('fire', 'selectPrev', step, {
				stepData: $(step).data('stepData')
			}))
			.add( jmpress.jmpress('fire', 'selectNext', step, {
				stepData: $(step).data('stepData')
			}));
		siblings.each(function() {
			var step = this,
				stepData = $(step).data("stepData");
			if(!stepData.src || stepData.srcLoaded) {
				return;
			}
			jmpress.jmpress('fire', loadStep, step, {
				stepData: $(step).data('stepData')
			});
		});
	});
	$jmpress("setActive", function(step, eventData) {
		var stepData = $(step).data("stepData");
		if(!stepData.src || stepData.srcLoaded) {
			return;
		}
		$(this).jmpress('fire', loadStep, step, {
			stepData: $(step).data('stepData')
		});
	});

}(jQuery, document, window));
/*
 * hash.js
 * Detect and set the URL hash
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress,
		hashLink = "a[href^='#']";

	/* FUNCTIONS */
	function randomString() {
		return "" + Math.round(Math.random() * 100000, 0);
	}
	/**
	 * getElementFromUrl
	 *
	 * @return String or undefined
	 */
	function getElementFromUrl(settings) {
		// get id from url # by removing `#` or `#/` from the beginning,
		// so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
		// TODO SECURITY check user input to be valid!
		try {
			var el = $( '#' + window.location.hash.replace(/^#\/?/,"") );
			return el.length > 0 && el.is(settings.stepSelector) ? el : undefined;
		} catch(e) {}
	}
	function setHash(stepid) {
		var shouldBeHash = "#/" + stepid;
		if(window.history && window.history.pushState) {
			// shouldBeHash = "#" + stepid;
			// consider this for future versions
			//  it has currently issues, when startup with a link with hash (webkit)
			if(window.location.hash !== shouldBeHash) {
				window.history.pushState({}, '', shouldBeHash);
			}
		} else {
			if(window.location.hash !== shouldBeHash) {
				window.location.hash = shouldBeHash;
			}
		}
	}

	/* DEFAULTS */
	$jmpress('defaults').hash = {
		use: true
		,update: true
		,bindChange: true
		// NOTICE: {use: true, update: false, bindChange: true}
		// will cause a error after clicking on a link to the current step
	};

	/* HOOKS */
	$jmpress('selectInitialStep', function( step, eventData ) {
		var settings = eventData.settings,
			hashSettings = settings.hash,
			current = eventData.current,
			jmpress = $(this);
		eventData.current.hashNamespace = ".jmpress-"+randomString();
		// HASH CHANGE EVENT
		if ( hashSettings.use ) {
			if ( hashSettings.bindChange ) {
				$(window).bind('hashchange'+current.hashNamespace, function(event) {
					var urlItem = getElementFromUrl(settings);
					if ( jmpress.jmpress('initialized') ) {
						jmpress.jmpress("scrollFix");
					}
					if(urlItem && urlItem.length) {
						if(urlItem.attr("id") !== jmpress.jmpress("active").attr("id")) {
							jmpress.jmpress('select', urlItem);
						}
						setHash(urlItem.attr("id"));
					}
					event.preventDefault();
				});
				$(hashLink).on("click"+current.hashNamespace, function(event) {
					var href = $(this).attr("href");
					try {
						if($(href).is(settings.stepSelector)) {
							jmpress.jmpress("select", href);
							event.preventDefault();
							event.stopPropagation();
						}
					} catch(e) {}
				});
			}
			return getElementFromUrl(settings);
		}
	});
	$jmpress('afterDeinit', function( nil, eventData ) {
		$(hashLink).off(eventData.current.hashNamespace);
		$(window).unbind(eventData.current.hashNamespace);
	});
	$jmpress('setActive', function( step, eventData ) {
		var settings = eventData.settings,
			current = eventData.current;
		// `#/step-id` is used instead of `#step-id` to prevent default browser
		// scrolling to element in hash
		if ( settings.hash.use && settings.hash.update ) {
			clearTimeout(current.hashtimeout);
			current.hashtimeout = setTimeout(function() {
				setHash($(eventData.delegatedFrom).attr('id'));
			}, settings.transitionDuration + 200);
		}
	});

}(jQuery, document, window));
/*
 * keyboard.js
 * Keyboard event mapping and default keyboard actions
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress,
		jmpressNext = "next",
		jmpressPrev = "prev";

	/* FUNCTIONS */
	function randomString() {
		return "" + Math.round(Math.random() * 100000, 0);
	}
	function stopEvent(event) {
		event.preventDefault();
		event.stopPropagation();
	}

	/* DEFAULTS */
	$jmpress('defaults').keyboard = {
		use: true
		,keys: {
			33: jmpressPrev // pg up
			,37: jmpressPrev // left
			,38: jmpressPrev // up

			,9: jmpressNext+":"+jmpressPrev // tab
			,32: jmpressNext // space
			,34: jmpressNext // pg down
			,39: jmpressNext // right
			,40: jmpressNext // down

			,36: "home" // home

			,35: "end" // end
		}
		,ignore: {
			"INPUT": [
				32 // space
				,37 // left
				,38 // up
				,39 // right
				,40 // down
			]
			,"TEXTAREA": [
				32 // space
				,37 // left
				,38 // up
				,39 // right
				,40 // down
			]
			,"SELECT": [
				38 // up
				,40 // down
			]
		}
		,tabSelector: "a[href]:visible, :input:visible"
	};

	/* HOOKS */
	$jmpress('afterInit', function( nil, eventData ) {
		var settings = eventData.settings,
			keyboardSettings = settings.keyboard,
			ignoreKeyboardSettings = keyboardSettings.ignore,
			current = eventData.current,
			jmpress = $(this);

		// tabindex make it focusable so that it can receive key events
		if(!settings.fullscreen) {
			jmpress.attr("tabindex", 0);
		}

		current.keyboardNamespace = ".jmpress-"+randomString();

		// KEYPRESS EVENT: this fixes a Opera bug
		$(settings.fullscreen ? document : jmpress)
			.bind("keypress"+current.keyboardNamespace, function( event ) {

			for( var nodeName in ignoreKeyboardSettings ) {
				if ( event.target.nodeName === nodeName && ignoreKeyboardSettings[nodeName].indexOf(event.which) !== -1 ) {
					return;
				}
			}
			if(event.which >= 37 && event.which <= 40 || event.which === 32) {
				stopEvent(event);
			}
		});
		// KEYDOWN EVENT
		$(settings.fullscreen ? document : jmpress)
			.bind("keydown"+current.keyboardNamespace, function( event ) {
			var eventTarget = $(event.target);

			if ( !settings.fullscreen && !eventTarget.closest(jmpress).length || !keyboardSettings.use ) {
				return;
			}

			for( var nodeName in ignoreKeyboardSettings ) {
				if ( eventTarget[0].nodeName === nodeName && ignoreKeyboardSettings[nodeName].indexOf(event.which) !== -1 ) {
					return;
				}
			}

			var reverseSelect = false;
			var nextFocus;
			if (event.which === 9) {
				// tab
				if ( !eventTarget.closest( jmpress.jmpress('active') ).length ) {
					if ( !event.shiftKey ) {
						nextFocus = jmpress.jmpress('active').find("a[href], :input").filter(":visible").first();
					} else {
						reverseSelect = true;
					}
				} else {
					nextFocus = eventTarget.near( keyboardSettings.tabSelector, event.shiftKey );
					if( !$(nextFocus)
						.closest( settings.stepSelector )
						.is(jmpress.jmpress('active') ) ) {
						nextFocus = undefined;
					}
				}
				if( nextFocus && nextFocus.length > 0 ) {
					nextFocus.focus();
					jmpress.jmpress("scrollFix");
					stopEvent(event);
					return;
				} else {
					if(event.shiftKey) {
						reverseSelect = true;
					}
				}
			}

			var action = keyboardSettings.keys[ event.which ];
			if ( typeof action === "string" ) {
				if (action.indexOf(":") !== -1) {
					action = action.split(":");
					action = event.shiftKey ? action[1] : action[0];
				}
				jmpress.jmpress( action );
				stopEvent(event);
			} else if ( $.isFunction(action) ) {
				action.call(jmpress, event);
			} else if ( action ) {
				jmpress.jmpress.apply( jmpress, action );
				stopEvent(event);
			}

			if (reverseSelect) {
				// tab
				nextFocus = jmpress.jmpress('active').find("a[href], :input").filter(":visible").last();
				nextFocus.focus();
				jmpress.jmpress("scrollFix");
			}
		});
	});
	$jmpress('afterDeinit', function( nil, eventData ) {
		$(document).unbind(eventData.current.keyboardNamespace);
	});


}(jQuery, document, window));
/*
 * viewport.js
 * Scale to fit a given viewport
 */
(function( $, document, window, undefined ) {

	'use strict';

	function randomString() {
		return "" + Math.round(Math.random() * 100000, 0);
	}

	var browser = (function() {
		var ua = navigator.userAgent.toLowerCase();
		var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
			/(webkit)[ \/]([\w.]+)/.exec(ua) ||
			/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
			/(msie) ([\w.]+)/.exec(ua) ||
			ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
			[];
		return match[1] || "";
	}());

	var defaults = $.jmpress("defaults");
	defaults.viewPort = {
		width: false
		,height: false
		,maxScale: 0
		,minScale: 0
		,zoomable: 0
		,zoomBindMove: true
		,zoomBindWheel: true
	};
	var keys = defaults.keyboard.keys;
	keys[browser === 'mozilla' ? 107 : 187] = "zoomIn";  // +
	keys[browser === 'mozilla' ? 109 : 189] = "zoomOut"; // -
	defaults.reasonableAnimation.resize = {
		transitionDuration: '0s'
		,transitionDelay: '0ms'
	};
	defaults.reasonableAnimation.zoom = {
		transitionDuration: '0s'
		,transitionDelay: '0ms'
	};
	$.jmpress("initStep", function( step, eventData ) {
		for(var variable in {"viewPortHeight":1, "viewPortWidth":1, "viewPortMinScale":1, "viewPortMaxScale":1, "viewPortZoomable":1}) {
			eventData.stepData[variable] = eventData.data[variable] && parseFloat(eventData.data[variable]);
		}
	});
	$.jmpress("afterInit", function( nil, eventData ) {
		var jmpress = this;
		eventData.current.viewPortNamespace = ".jmpress-"+randomString();
		$(window).bind("resize"+eventData.current.viewPortNamespace, function (event) {
			$(jmpress).jmpress("reselect", "resize");
		});
		eventData.current.userZoom = 0;
		eventData.current.userTranslateX = 0;
		eventData.current.userTranslateY = 0;
		if(eventData.settings.viewPort.zoomBindWheel) {
			$(eventData.settings.fullscreen ? document : this)
				.bind("mousewheel"+eventData.current.viewPortNamespace+" DOMMouseScroll"+eventData.current.viewPortNamespace, function( event, delta ) {
				delta = delta || event.originalEvent.wheelDelta || -event.originalEvent.detail /* mozilla */;
				var direction = (delta / Math.abs(delta));
				if(direction < 0) {
					$(eventData.jmpress).jmpress("zoomOut", event.originalEvent.x, event.originalEvent.y);
				} else if(direction > 0) {
					$(eventData.jmpress).jmpress("zoomIn", event.originalEvent.x, event.originalEvent.y);
				}
				return false;
			});
		}
		if(eventData.settings.viewPort.zoomBindMove) {
			$(eventData.settings.fullscreen ? document : this).bind("mousedown"+eventData.current.viewPortNamespace, function (event) {
				if(eventData.current.userZoom) {
					eventData.current.userTranslating = { x: event.clientX, y: event.clientY };
					event.preventDefault();
					event.stopImmediatePropagation();
				}
			}).bind("mousemove"+eventData.current.viewPortNamespace, function (event) {
				var userTranslating = eventData.current.userTranslating;
				if(userTranslating) {
					$(jmpress).jmpress("zoomTranslate", event.clientX - userTranslating.x, event.clientY - userTranslating.y);
					userTranslating.x = event.clientX;
					userTranslating.y = event.clientY;
					event.preventDefault();
					event.stopImmediatePropagation();
				}
			}).bind("mouseup"+eventData.current.viewPortNamespace, function (event) {
				if(eventData.current.userTranslating) {
					eventData.current.userTranslating = undefined;
					event.preventDefault();
					event.stopImmediatePropagation();
				}
			});
		}
	});
	function maxAbs(value, range) {
		return Math.max(Math.min(value, range), -range);
	}
	function zoom(x, y, direction) {
		var current = $(this).jmpress("current"),
			settings = $(this).jmpress("settings"),
			stepData = $(this).jmpress("active").data("stepData"),
			container = $(this).jmpress("container");
		if(current.userZoom === 0 && direction < 0) {
			return;
		}
		var zoomableSteps = stepData.viewPortZoomable || settings.viewPort.zoomable;
		if(current.userZoom === zoomableSteps && direction > 0) {
			return;
		}
		current.userZoom += direction;

		var halfWidth = $(container).innerWidth()/2,
			halfHeight = $(container).innerHeight()/2;

		x = x ? x - halfWidth : x;
		y = y ? y - halfHeight : y;

		// TODO this is not perfect... too much math... :(
		current.userTranslateX =
			maxAbs(current.userTranslateX - direction * x / current.zoomOriginWindowScale / zoomableSteps,
			halfWidth * current.userZoom * current.userZoom / zoomableSteps);
		current.userTranslateY =
			maxAbs(current.userTranslateY - direction * y / current.zoomOriginWindowScale / zoomableSteps,
			halfHeight * current.userZoom * current.userZoom / zoomableSteps);

		$(this).jmpress("reselect", "zoom");
	}
	$.jmpress("register", "zoomIn", function(x, y) {
		zoom.call(this, x||0, y||0, 1);
	});
	$.jmpress("register", "zoomOut", function(x, y) {
		zoom.call(this, x||0, y||0, -1);
	});
	$.jmpress("register", "zoomTranslate", function(x, y) {
		var current = $(this).jmpress("current"),
			settings = $(this).jmpress("settings"),
			stepData = $(this).jmpress("active").data("stepData"),
			container = $(this).jmpress("container");
		var zoomableSteps = stepData.viewPortZoomable || settings.viewPort.zoomable;
		var halfWidth = $(container).innerWidth(),
			halfHeight = $(container).innerHeight();
		current.userTranslateX =
			maxAbs(current.userTranslateX + x / current.zoomOriginWindowScale,
			halfWidth * current.userZoom * current.userZoom / zoomableSteps);
		current.userTranslateY =
			maxAbs(current.userTranslateY + y / current.zoomOriginWindowScale,
			halfHeight * current.userZoom * current.userZoom / zoomableSteps);
		$(this).jmpress("reselect", "zoom");
	});
	$.jmpress('afterDeinit', function( nil, eventData ) {
		$(eventData.settings.fullscreen ? document : this).unbind(eventData.current.viewPortNamespace);
		$(window).unbind(eventData.current.viewPortNamespace);
	});
	$.jmpress("setActive", function( step, eventData ) {
		var viewPort = eventData.settings.viewPort;
		var viewPortHeight = eventData.stepData.viewPortHeight || viewPort.height;
		var viewPortWidth = eventData.stepData.viewPortWidth || viewPort.width;
		var viewPortMaxScale = eventData.stepData.viewPortMaxScale || viewPort.maxScale;
		var viewPortMinScale = eventData.stepData.viewPortMinScale || viewPort.minScale;
		// Correct the scale based on the window's size
		var windowScaleY = viewPortHeight && $(eventData.container).innerHeight()/viewPortHeight;
		var windowScaleX = viewPortWidth && $(eventData.container).innerWidth()/viewPortWidth;
		var windowScale = (windowScaleX || windowScaleY) && Math.min( windowScaleX || windowScaleY, windowScaleY || windowScaleX );

		if(windowScale) {
			windowScale = windowScale || 1;
			if(viewPortMaxScale) {
				windowScale = Math.min(windowScale, viewPortMaxScale);
			}
			if(viewPortMinScale) {
				windowScale = Math.max(windowScale, viewPortMinScale);
			}

			var zoomableSteps = eventData.stepData.viewPortZoomable || eventData.settings.viewPort.zoomable;
			if(zoomableSteps) {
				var diff = (1/windowScale) - (1/viewPortMaxScale);
				diff /= zoomableSteps;
				windowScale = 1/((1/windowScale) - diff * eventData.current.userZoom);
			}

			eventData.target.transform.reverse();
			if(eventData.current.userTranslateX && eventData.current.userTranslateY) {
				eventData.target.transform.push(["translate", eventData.current.userTranslateX, eventData.current.userTranslateY, 0]);
			} else {
				eventData.target.transform.push(["translate"]);
			}
			eventData.target.transform.push(["scale",
				windowScale,
				windowScale,
				1]);
			eventData.target.transform.reverse();
			eventData.target.perspectiveScale /= windowScale;
		}
		eventData.current.zoomOriginWindowScale = windowScale;
	});
	$.jmpress("setInactive", function( step, eventData ) {
		if(!eventData.nextStep || !step || $(eventData.nextStep).attr("id") !== $(step).attr("id")) {
			eventData.current.userZoom = 0;
			eventData.current.userTranslateX = 0;
			eventData.current.userTranslateY = 0;
		}
	});

}(jQuery, document, window));

/*
 * mouse.js
 * Clicking to select a step
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress;

	/* FUNCTIONS */
	function randomString() {
		return "" + Math.round(Math.random() * 100000, 0);
	}

	/* DEFAULTS */
	$jmpress("defaults").mouse = {
		clickSelects: true
	};

	/* HOOKS */
	$jmpress("afterInit", function( nil, eventData ) {
		var settings = eventData.settings,
			stepSelector = settings.stepSelector,
			current = eventData.current,
			jmpress = $(this);
		current.clickableStepsNamespace = ".jmpress-"+randomString();
		jmpress.bind("click"+current.clickableStepsNamespace, function(event) {
			if (!settings.mouse.clickSelects || current.userZoom) {
				return;
			}

			// get clicked step
			var clickedStep = $(event.target).closest(stepSelector);

			// clicks on the active step do default
			if ( clickedStep.is( jmpress.jmpress("active") ) ) {
				return;
			}

			if (clickedStep.length) {
				// select the clicked step
				jmpress.jmpress("select", clickedStep[0], "click");
				event.preventDefault();
				event.stopPropagation();
			}
		});
	});
	$jmpress('afterDeinit', function( nil, eventData ) {
		$(this).unbind(eventData.current.clickableStepsNamespace);
	});

}(jQuery, document, window));
/*
 * mobile.js
 * Adds support for swipe on touch supported browsers
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress;

	/* FUNCTIONS */
	function randomString() {
		return "" + Math.round(Math.random() * 100000, 0);
	}

	/* HOOKS */
	$jmpress( 'afterInit', function( step, eventData ) {
		var settings = eventData.settings,
			current = eventData.current,
			jmpress = eventData.jmpress;
		current.mobileNamespace = ".jmpress-"+randomString();
		var data, start = [0,0];
		$(settings.fullscreen ? document : jmpress)
			.bind("touchstart"+current.mobileNamespace, function( event ) {

			data = event.originalEvent.touches[0];
			start = [ data.pageX, data.pageY ];

		}).bind("touchmove"+current.mobileNamespace, function( event ) {
			data = event.originalEvent.touches[0];
			event.preventDefault();
			return false;
		}).bind("touchend"+current.mobileNamespace, function( event ) {
			var end = [ data.pageX, data.pageY ],
				diff = [ end[0]-start[0], end[1]-start[1] ];

			if(Math.max(Math.abs(diff[0]), Math.abs(diff[1])) > 50) {
				diff = Math.abs(diff[0]) > Math.abs(diff[1]) ? diff[0] : diff[1];
				$(jmpress).jmpress(diff > 0 ? "prev" : "next");
				event.preventDefault();
				return false;
			}
		});
	});
	$jmpress('afterDeinit', function( nil, eventData ) {
		var settings = eventData.settings,
			current = eventData.current,
			jmpress = eventData.jmpress;
		$(settings.fullscreen ? document : jmpress).unbind(current.mobileNamespace);
	});

}(jQuery, document, window));
/*
 * templates.js
 * The amazing template engine
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress,
		templateFromParentIdent = "_template_",
		templateFromApplyIdent = "_applied_template_";

	/* STATIC VARS */
	var templates = {};

	/* FUNCTIONS */
	function addUndefined( target, values, prefix ) {
		for( var name in values ) {
			var targetName = name;
			if ( prefix ) {
				targetName = prefix + targetName.substr(0, 1).toUpperCase() + targetName.substr(1);
			}
			if ( $.isPlainObject(values[name]) ) {
				addUndefined( target, values[name], targetName );
			} else if( target[targetName] === undefined ) {
				target[targetName] = values[name];
			}
		}
	}
	function applyChildrenTemplates( children, templateChildren ) {
		if ($.isArray(templateChildren)) {
			if (templateChildren.length < children.length) {
				$.error("more nested steps than children in template");
			} else {
				children.each(function(idx, child) {
					child = $(child);
					var tmpl = child.data(templateFromParentIdent) || {};
					addUndefined(tmpl, templateChildren[idx]);
					child.data(templateFromParentIdent, tmpl);
				});
			}
		} else if($.isFunction(templateChildren)) {
			children.each(function(idx, child) {
				child = $(child);
				var tmpl = child.data(templateFromParentIdent) || {};
				addUndefined(tmpl, templateChildren(idx, child, children));
				child.data(templateFromParentIdent, tmpl);
			});
		} // TODO: else if(object)
	}
	function applyTemplate( data, element, template, eventData ) {
		if (template.children) {
			var children = element.children( eventData.settings.stepSelector );
			applyChildrenTemplates( children, template.children );
		}
		applyTemplateData( data, template );
	}
	function applyTemplateData( data, template ) {
		addUndefined(data, template);
	}

	/* HOOKS */
	$jmpress("beforeInitStep", function( step, eventData ) {
		step = $(step);
		var data = eventData.data,
			templateFromAttr = data.template,
			templateFromApply = step.data(templateFromApplyIdent),
			templateFromParent = step.data(templateFromParentIdent);
		if(templateFromAttr) {
			$.each(templateFromAttr.split(" "), function(idx, tmpl) {
				var template = templates[tmpl];
				applyTemplate( data, step, template, eventData );
			});
		}
		if (templateFromApply) {
			applyTemplate( data, step, templateFromApply, eventData );
		}
		if (templateFromParent) {
			applyTemplate( data, step, templateFromParent, eventData );
			step.data(templateFromParentIdent, null);
			if(templateFromParent.template) {
				$.each(templateFromParent.template.split(" "), function(idx, tmpl) {
					var template = templates[tmpl];
					applyTemplate( data, step, template, eventData );
				});
			}
		}
	});
	$jmpress("beforeInit", function( nil, eventData ) {
		var data = $jmpress("dataset", this),
			dataTemplate = data.template,
			stepSelector = eventData.settings.stepSelector;
		if (dataTemplate) {
			var template = templates[dataTemplate];
			applyChildrenTemplates( $(this).find(stepSelector).filter(function() {
				return !$(this).parent().is(stepSelector);
			}), template.children );
		}
	});

	/* EXPORTED FUNCTIONS */
	$jmpress("register", "template", function( name, tmpl ) {
		if (templates[name]) {
			templates[name] = $.extend(true, {}, templates[name], tmpl);
		} else {
			templates[name] = $.extend(true, {}, tmpl);
		}
	});
	$jmpress("register", "apply", function( selector, tmpl ) {
		if( !tmpl ) {
			// TODO ERROR because settings not found
			var stepSelector = $(this).jmpress("settings").stepSelector;
			applyChildrenTemplates( $(this).find(stepSelector).filter(function() {
				return !$(this).parent().is(stepSelector);
			}), selector );
		} else if($.isArray(tmpl)) {
			applyChildrenTemplates( $(selector), tmpl );
		} else {
			var template;
			if(typeof tmpl === "string") {
				template = templates[tmpl];
			} else {
				template = $.extend(true, {}, tmpl);
			}
			$(selector).each(function(idx, element) {
				element = $(element);
				var tmpl = element.data(templateFromApplyIdent) || {};
				addUndefined(tmpl, template);
				element.data(templateFromApplyIdent, tmpl);
			});
		}
	});

}(jQuery, document, window));
/*
 * jqevents.js
 * Fires jQuery events
 */
(function( $, document, window, undefined ) {

	'use strict';

	/* HOOKS */
	// the events should not bubble up the tree
	// elsewise nested jmpress would cause buggy behavior
	$.jmpress("setActive", function( step, eventData ) {
		if(eventData.prevStep !== step) {
			$(step).triggerHandler("enterStep");
		}
	});
	$.jmpress("setInactive", function( step, eventData ) {
		if(eventData.nextStep !== step) {
			$(step).triggerHandler("leaveStep");
		}
	});

}(jQuery, document, window));
/*
 * animation.js
 * Apply custom animations to steps
 */
(function( $, document, window, undefined ) {

	'use strict';

	function parseSubstepInfo(str) {
		var arr = str.split(" ");
		var className = arr[0];
		var config = { willClass: "will-"+className, doClass: "do-"+className, hasClass: "has-"+className };
		var state = "";
		for(var i = 1; i < arr.length; i++) {
			var s = arr[i];
			switch(state) {
			case "":
				if(s === "after") {
					state = "after";
				} else {
					$.warn("unknown keyword in '"+str+"'. '"+s+"' unknown.");
				}
				break;
			case "after":
				if(s.match(/^[1-9][0-9]*m?s?/)) {
					var value = parseFloat(s);
					if(s.indexOf("ms") !== -1) {
						value *= 1;
					} else if(s.indexOf("s") !== -1) {
						value *= 1000;
					} else if(s.indexOf("m") !== -1) {
						value *= 60000;
					}
					config.delay = value;
				} else {
					config.after = Array.prototype.slice.call(arr, i).join(" ");
					i = arr.length;
				}
			}
		}
		return config;
	}
	function find(array, selector, start, end) {
		end = end || (array.length - 1);
		start = start || 0;
		for(var i = start; i < end + 1; i++) {
			if($(array[i].element).is(selector)) {
				return i;
			}
		}
	}
	function addOn(list, substep, delay) {
		$.each(substep._on, function(idx, child) {
			list.push({substep: child.substep, delay: child.delay + delay});
			addOn(list, child.substep, child.delay + delay);
		});
	}
	$.jmpress("defaults").customAnimationDataAttribute = "jmpress";
	$.jmpress("afterInit", function( nil, eventData ) {
		eventData.current.animationTimeouts = [];
		eventData.current.animationCleanupWaiting = [];
	});
	$.jmpress("applyStep", function( step, eventData ) {
		// read custom animation from elements
		var substepsData = {};
		var listOfSubsteps = [];
		$(step).find("[data-"+eventData.settings.customAnimationDataAttribute+"]")
				.each(function(idx, element) {
			if($(element).closest(eventData.settings.stepSelector).is(step)) {
				listOfSubsteps.push({element: element});
			}
		});
		if(listOfSubsteps.length === 0) {
			return;
		}
		$.each(listOfSubsteps, function(idx, substep) {
			substep.info = parseSubstepInfo(
				$(substep.element).data(eventData.settings.customAnimationDataAttribute));
			$(substep.element).addClass(substep.info.willClass);
			substep._on = [];
			substep._after = null;
		});
		var current = {_after: undefined, _on: [], info: {}}; // virtual zero step
		$.each(listOfSubsteps, function(idx, substep) {
			var other = substep.info.after;
			if(other) {
				if(other === "step") {
					other = current;
				} else if(other === "prev") {
					other = listOfSubsteps[idx-1];
				} else {
					var index = find(listOfSubsteps, other, 0, idx - 1);
					if(index === undefined) {
						index = find(listOfSubsteps, other);
					}
					other = (index === undefined || index === idx) ? listOfSubsteps[idx-1] : listOfSubsteps[index];
				}
			} else {
				other = listOfSubsteps[idx-1];
			}
			if(other) {
				if(!substep.info.delay) {
					if(!other._after) {
						other._after = substep;
						return;
					}
					other = other._after;
				}
				other._on.push({substep: substep, delay: substep.info.delay || 0});
			}
		});
		if(current._after === undefined && current._on.length === 0) {
			var startStep = find(listOfSubsteps, eventData.stepData.startSubstep) || 0;
			current._after = listOfSubsteps[startStep];
		}
		var substepsInOrder = [];
		function findNextFunc(idx, item) {
			if(item.substep._after) {
				current = item.substep._after;
				return false;
			}
		}
		do {
			var substepList = [{substep: current, delay: 0}];
			addOn(substepList, current, 0);
			substepsInOrder.push(substepList);
			current = null;
			$.each(substepList, findNextFunc);
		} while(current);
		substepsData.list = substepsInOrder;
		$(step).data("substepsData", substepsData);
	});
	$.jmpress("unapplyStep", function( step, eventData ) {
		var substepsData = $(step).data("substepsData");
		if(substepsData) {
			$.each(substepsData.list, function(idx, activeSubsteps) {
				$.each(activeSubsteps, function(idx, substep) {
					if(substep.substep.info.willClass) {
						$(substep.substep.element).removeClass(substep.substep.info.willClass);
					}
					if(substep.substep.info.hasClass) {
						$(substep.substep.element).removeClass(substep.substep.info.hasClass);
					}
					if(substep.substep.info.doClass) {
						$(substep.substep.element).removeClass(substep.substep.info.doClass);
					}
				});
			});
		}
	});
	$.jmpress("setActive", function(step, eventData) {
		var substepsData = $(step).data("substepsData");
		if(!substepsData) {
			return;
		}
		if(eventData.substep === undefined) {
			eventData.substep =
				(eventData.reason === "prev" ?
					substepsData.list.length-1 :
					0
				);
		}
		var substep = eventData.substep;
		$.each(eventData.current.animationTimeouts, function(idx, timeout) {
			clearTimeout(timeout);
		});
		eventData.current.animationTimeouts = [];
		$.each(substepsData.list, function(idx, activeSubsteps) {
			var applyHas = idx < substep;
			var applyDo = idx <= substep;
			$.each(activeSubsteps, function(idx, substep) {
				if(substep.substep.info.hasClass) {
					$(substep.substep.element)[(applyHas?"add":"remove")+"Class"](substep.substep.info.hasClass);
				}
				function applyIt() {
					$(substep.substep.element).addClass(substep.substep.info.doClass);
				}
				if(applyDo && !applyHas && substep.delay && eventData.reason !== "prev") {
					if(substep.substep.info.doClass) {
						$(substep.substep.element).removeClass(substep.substep.info.doClass);
						eventData.current.animationTimeouts.push(setTimeout(applyIt, substep.delay));
					}
				} else {
					if(substep.substep.info.doClass) {
						$(substep.substep.element)[(applyDo?"add":"remove")+"Class"](substep.substep.info.doClass);
					}
				}
			});
		});
	});
	$.jmpress("setInactive", function(step, eventData) {
		if(eventData.nextStep === step) {
			return;
		}
		function cleanupAnimation( substepsData ) {
			$.each(substepsData.list, function(idx, activeSubsteps) {
				$.each(activeSubsteps, function(idx, substep) {
					if(substep.substep.info.hasClass) {
						$(substep.substep.element).removeClass(substep.substep.info.hasClass);
					}
					if(substep.substep.info.doClass) {
						$(substep.substep.element).removeClass(substep.substep.info.doClass);
					}
				});
			});
		}
		$.each(eventData.current.animationCleanupWaiting, function(idx, item) {
			cleanupAnimation(item);
		});
		eventData.current.animationCleanupWaiting = [];
		var substepsData = $(step).data("substepsData");
		if(substepsData) {
			eventData.current.animationCleanupWaiting.push( substepsData );
		}
	});
	$.jmpress("selectNext", function( step, eventData ) {
		if(eventData.substep === undefined) {
			return;
		}
		var substepsData = $(step).data("substepsData");
		if(!substepsData) {
			return;
		}
		if(eventData.substep < substepsData.list.length-1) {
			return {step: step, substep: eventData.substep+1};
		}
	});
	$.jmpress("selectPrev", function( step, eventData ) {
		if(eventData.substep === undefined) {
			return;
		}
		var substepsData = $(step).data("substepsData");
		if(!substepsData) {
			return;
		}
		if(eventData.substep > 0) {
			return {step: step, substep: eventData.substep-1};
		}
	});

}(jQuery, document, window));
/*!
 * plugin for jmpress.js v0.4.5
 *
 * Copyright 2013 Kyle Robinson Young @shama & Tobias Koppers @sokra
 * Licensed MIT
 * http://www.opensource.org/licenses/mit-license.php
 *//*
 * jmpress.toggle plugin
 * For binding a key to toggle de/initialization of jmpress.js.
 */
(function( $, document, window, undefined ) {
	'use strict';
	$.jmpress("register", "toggle", function( key, config, initial ) {
		var jmpress = this;
		$(document).bind("keydown", function( event ) {
			if ( event.keyCode === key ) {
				if ($(jmpress).jmpress("initialized")) {
					$(jmpress).jmpress("deinit");
				} else {
					$(jmpress).jmpress(config);
				}
			}
		});
		if ( initial ) {
			$(jmpress).jmpress(config);
		}
	});
}(jQuery, document, window));

/*
 * jmpress.secondary plugin
 * Apply a secondary animation when step is selected.
 */
(function( $, document, window, undefined ) {
	'use strict';
	$.jmpress("initStep", function( step, eventData ) {
		for(var name in eventData.data) {
			if(name.indexOf("secondary") === 0) {
				eventData.stepData[name] = eventData.data[name];
			}
		}
	});
	function exchangeIf(childStepData, condition, step) {
		if(childStepData.secondary &&
			childStepData.secondary.split(" ").indexOf(condition) !== -1) {
			for(var name in childStepData) {
				if(name.length > 9 && name.indexOf("secondary") === 0) {
					var tmp = childStepData[name];
					var normal = name.substr(9);
					normal = normal.substr(0, 1).toLowerCase() + normal.substr(1);
					childStepData[name] = childStepData[normal];
					childStepData[normal] = tmp;
				}
			}
			$(this).jmpress("reapply", $(step));
		}
	}
	$.jmpress("beforeActive", function( step, eventData ) {
		exchangeIf.call(eventData.jmpress, $(step).data("stepData"), "self", step);
		var parent = $(step).parent();
		$(parent)
			.children(eventData.settings.stepSelector)
			.each(function(idx, child) {
				var childStepData = $(child).data("stepData");
				exchangeIf.call(eventData.jmpress, childStepData, "siblings", child);
			});
		function grandchildrenFunc(idx, child) {
			var childStepData = $(child).data("stepData");
			exchangeIf.call(eventData.jmpress, childStepData, "grandchildren", child);
		}
		for(var i = 1; i < eventData.parents.length; i++) {
			$(eventData.parents[i])
				.children(eventData.settings.stepSelector)
				.each();
		}
	});
	$.jmpress("setInactive", function( step, eventData ) {
		exchangeIf.call(eventData.jmpress, $(step).data("stepData"), "self", step);
		var parent = $(step).parent();
		$(parent)
			.children(eventData.settings.stepSelector)
			.each(function(idx, child) {
				var childStepData = $(child).data("stepData");
				exchangeIf.call(eventData.jmpress, childStepData, "siblings", child);
			});
		function grandchildrenFunc(idx, child) {
			var childStepData = $(child).data("stepData");
			exchangeIf.call(eventData.jmpress, childStepData, "grandchildren", child);
		}
		for(var i = 1; i < eventData.parents.length; i++) {
			$(eventData.parents[i])
				.children(eventData.settings.stepSelector)
				.each(grandchildrenFunc);
		}
	});
}(jQuery, document, window));

/*
 * jmpress.duration plugin
 * For auto advancing steps after a given duration and optionally displaying a
 * progress bar.
 */
(function( $, document, window, undefined ) {
	'use strict';

	$.jmpress("defaults").duration = {
		defaultValue: -1
		,defaultAction: "next"
		,barSelector: undefined
		,barProperty: "width"
		,barPropertyStart: "0"
		,barPropertyEnd: "100%"
	};
	$.jmpress("initStep", function( step, eventData ) {
		eventData.stepData.duration = eventData.data.duration && parseInt(eventData.data.duration, 10);
		eventData.stepData.durationAction = eventData.data.durationAction;
	});
	$.jmpress("setInactive", function( step, eventData ) {
		var settings = eventData.settings,
			durationSettings = settings.duration,
			current = eventData.current;
		var dur = eventData.stepData.duration || durationSettings.defaultValue;
		if( current.durationTimeout ) {
			if( durationSettings.barSelector ) {
				var css = {
					transitionProperty: durationSettings.barProperty
					,transitionDuration: '0'
					,transitionDelay: '0'
					,transitionTimingFunction: 'linear'
				};
				css[durationSettings.barProperty] = durationSettings.barPropertyStart;
				var bars = $(durationSettings.barSelector);
				$.jmpress("css", bars, css);
				bars.each(function(idx, element) {
					var next = $(element).next();
					var parent = $(element).parent();
					$(element).detach();
					if(next.length) {
						next.insertBefore(element);
					} else {
						parent.append(element);
					}
				});
			}
			clearTimeout(current.durationTimeout);
			delete current.durationTimeout;
		}
	});
	$.jmpress("setActive", function( step, eventData ) {
		var settings = eventData.settings,
			durationSettings = settings.duration,
			current = eventData.current;
		var dur = eventData.stepData.duration || durationSettings.defaultValue;
		if( dur && dur > 0 ) {
			if( durationSettings.barSelector ) {
				var css = {
					transitionProperty: durationSettings.barProperty
					,transitionDuration: (dur-settings.transitionDuration*2/3-100)+"ms"
					,transitionDelay: (settings.transitionDuration*2/3)+'ms'
					,transitionTimingFunction: 'linear'
				};
				css[durationSettings.barProperty] = durationSettings.barPropertyEnd;
				$.jmpress("css", $(durationSettings.barSelector), css);
			}
			var jmpress = this;
			if(current.durationTimeout) {
				clearTimeout(current.durationTimeout);
				current.durationTimeout = undefined;
			}
			current.durationTimeout = setTimeout(function() {
				var action = eventData.stepData.durationAction || durationSettings.defaultAction;
				$(jmpress).jmpress(action);
			}, dur);
		}
	});
}(jQuery, document, window));

/*
 * jmpress.presentation-mode plugin
 * Display a window for the presenter with notes and a control and view of the
 * presentation
 */
(function( $, document, window, undefined ) {

	'use strict';
	var $jmpress = $.jmpress;

	var PREFIX = "jmpress-presentation-";

	/* FUNCTIONS */
	function randomString() {
		return "" + Math.round(Math.random() * 100000, 0);
	}

	/* DEFAULTS */
	$jmpress("defaults").presentationMode = {
		use: true,
		url: "presentation-screen.html",
		notesUrl: false,
		transferredValues: ["userZoom", "userTranslateX", "userTranslateY"]
	};
	$jmpress("defaults").keyboard.keys[80] = "presentationPopup"; // p key

	/* HOOKS */
	$jmpress("afterInit", function( nil, eventData) {
		var current = eventData.current;

		current.selectMessageListeners = [];

		if(eventData.settings.presentationMode.use) {

			window.addEventListener("message", function(event) {
				// We do not test orgin, because we want to accept messages
				// from all orgins
				try {
					if(typeof event.data !== "string" || event.data.indexOf(PREFIX) !== 0) {
						return;
					}
					var json = JSON.parse(event.data.slice(PREFIX.length));
					switch(json.type) {
					case "select":
						$.each(eventData.settings.presentationMode.transferredValues, function(idx, name) {
							eventData.current[name] = json[name];
						});
						if(/[a-z0-9\-]+/i.test(json.targetId) && typeof json.substep in {number:1,undefined:1}) {
							$(eventData.jmpress).jmpress("select", {step: "#"+json.targetId, substep: json.substep}, json.reason);
						} else {
							$.error("For security reasons the targetId must match /[a-z0-9\\-]+/i and substep must be a number.");
						}
						break;
					case "listen":
						current.selectMessageListeners.push(event.source);
						break;
					case "ok":
						clearTimeout(current.presentationPopupTimeout);
						break;
					case "read":
						try {
							event.source.postMessage(PREFIX + JSON.stringify({type: "url", url: window.location.href, notesUrl: eventData.settings.presentationMode.notesUrl}), "*");
						} catch(e) {
							$.error("Cannot post message to source: " + e);
						}
						break;
					default:
						throw "Unknown message type: " + json.type;
					}
				} catch(e) {
					$.error("Received message is malformed: " + e);
				}
			});
			try {
				if(window.parent && window.parent !== window) {
					window.parent.postMessage(PREFIX + JSON.stringify({
						"type": "afterInit"
					}), "*");
				}
			} catch(e) {
				$.error("Cannot post message to parent: " + e);
			}
		}
	});
	$jmpress("afterDeinit", function( nil, eventData) {
		if(eventData.settings.presentationMode.use) {
			try {
				if(window.parent && window.parent !== window) {
					window.parent.postMessage(PREFIX + JSON.stringify({
						"type": "afterDeinit"
					}), "*");
				}
			} catch(e) {
				$.error("Cannot post message to parent: " + e);
			}
		}
	});
	$jmpress("setActive", function( step, eventData) {
		var stepId = $(eventData.delegatedFrom).attr("id"),
			substep = eventData.substep,
			reason = eventData.reason;
		$.each(eventData.current.selectMessageListeners, function(idx, listener) {
			try {
				var msg = {
					"type": "select",
					"targetId": stepId,
					"substep": substep,
					"reason": reason
				};
				$.each(eventData.settings.presentationMode.transferredValues, function(idx, name) {
					msg[name] = eventData.current[name];
				});
				listener.postMessage(PREFIX + JSON.stringify(msg), "*");
			} catch(e) {
				$.error("Cannot post message to listener: " + e);
			}
		});
	});
	$jmpress("register", "presentationPopup", function() {
		function trySend() {
			jmpress.jmpress("current").presentationPopupTimeout = setTimeout(trySend, 100);
			try {
				popup.postMessage(PREFIX + JSON.stringify({type: "url", url: window.location.href, notesUrl: jmpress.jmpress("settings").presentationMode.notesUrl}), "*");
			} catch(e) {
			}
		}
		var jmpress = $(this),
			popup;
		if(jmpress.jmpress("settings").presentationMode.use) {
			popup = window.open($(this).jmpress("settings").presentationMode.url);
			jmpress.jmpress("current").presentationPopupTimeout = setTimeout(trySend, 100);
		}
	});
}(jQuery, document, window));
