text: /*!
   * stroll.js 1.2 - CSS scroll effects
   * https://lab.hakim.se/scroll-effects
   * MIT licensed
   *
   * Copyright (C) 2012 Hakim El Hattab, https://hakim.se
   */
  (function(){
  
  	"use strict";
  
  	// When a list is configured as 'live', this is how frequently
  	// the DOM will be polled for changes
  	var LIVE_INTERVAL = 500;
  
  	var IS_TOUCH_DEVICE = !!( 'ontouchstart' in window );
  
  	// All of the lists that are currently bound
  	var lists = [];
  
  	// Set to true when there are lists to refresh
  	var active = false;
  
  	/**
  	 * Updates all currently bound lists.
  	 */
  	function refresh() {
  		if( active ) {
  			requestAnimFrame( refresh );
  
  			for( var i = 0, len = lists.length; i < len; i++ ) {
  				lists[i].update();
  			}
  		}
  	}
  
  	/**
  	 * Starts monitoring a list and applies classes to each of
  	 * its contained elements based on its position relative to
  	 * the list's viewport.
  	 *
  	 * @param {HTMLElement} element
  	 * @param {Object} options Additional arguments;
  	 * 	- live; Flags if the DOM should be repeatedly checked for changes
  	 * 			repeatedly. Useful if the list contents is changing. Use
  	 * 			scarcely as it has an impact on performance.
  	 */
  	function add( element, options ) {
  		// Only allow ul/ol
  		if( !element.nodeName || /^(ul|ol)$/i.test( element.nodeName ) === false ) {
  			return false;
  		}
  		// Delete duplicates (but continue and re-bind this list to get the
  		// latest properties and list items)
  		else if( contains( element ) ) {
  			remove( element );
  		}
  
  		var list = IS_TOUCH_DEVICE ? new TouchList( element ) : new List( element );
  
  		// Handle options
  		if( options && options.live ) {
  			list.syncInterval = setInterval( function() {
  				list.sync.call( list );
  			}, LIVE_INTERVAL );
  		}
  
  		// Synchronize the list with the DOM
  		list.sync();
  
  		// Add this element to the collection
  		lists.push( list );
  
  		// Start refreshing if this was the first list to be added
  		if( lists.length === 1 ) {
  			active = true;
  			refresh();
  		}
  	}
  
  	/**
  	 * Stops monitoring a list element and removes any classes
  	 * that were applied to its list items.
  	 *
  	 * @param {HTMLElement} element
  	 */
  	function remove( element ) {
  		for( var i = 0; i < lists.length; i++ ) {
  			var list = lists[i];
  
  			if( list.element == element ) {
  				list.destroy();
  				lists.splice( i, 1 );
  				i--;
  			}
  		}
  
  		// Stopped refreshing if the last list was removed
  		if( lists.length === 0 ) {
  			active = false;
  		}
  	}
  
  	/**
  	 * Checks if the specified element has already been bound.
  	 */
  	function contains( element ) {
  		for( var i = 0, len = lists.length; i < len; i++ ) {
  			if( lists[i].element == element ) {
  				return true;
  			}
  		}
  		return false;
  	}
  
  	/**
  	 * Calls 'method' for each DOM element discovered in
  	 * 'target'.
  	 *
  	 * @param target String selector / array of UL elements /
  	 * jQuery object / single UL element
  	 * @param method A function to call for each element target
  	 */
  	function batch( target, method, options ) {
  		var i, len;
  
  		// Selector
  		if( typeof target === 'string' ) {
  			var targets = document.querySelectorAll( target );
  
  			for( i = 0, len = targets.length; i < len; i++ ) {
  				method.call( null, targets[i], options );
  			}
  		}
  		// Array (jQuery)
  		else if( typeof target === 'object' && typeof target.length === 'number' ) {
  			for( i = 0, len = target.length; i < len; i++ ) {
  				method.call( null, target[i], options );
  			}
  		}
  		// Single element
  		else if( target.nodeName ) {
  			method.call( null, target, options );
  		}
  		else {
  			throw 'Stroll target was of unexpected type.';
  		}
  	}
  
  	/**
  	 * Checks if the client is capable of running the library.
  	 */
  	function isCapable() {
  		return !!document.body.classList;
  	}
  
  	/**
  	 * The basic type of list; applies past & future classes to
  	 * list items based on scroll state.
  	 */
  	function List( element ) {
  		this.element = element;
  	}
  
  	/**
  	 * Fetches the latest properties from the DOM to ensure that
  	 * this list is in sync with its contents.
  	 */
  	List.prototype.sync = function() {
  		this.items = Array.prototype.slice.apply( this.element.children );
  
  		// Caching some heights so we don't need to go back to the DOM so much
  		this.listHeight = this.element.offsetHeight;
  
  		// One loop to get the offsets from the DOM
  		for( var i = 0, len = this.items.length; i < len; i++ ) {
  			var item = this.items[i];
  			item._offsetHeight = item.offsetHeight;
  			item._offsetTop = item.offsetTop;
  			item._offsetBottom = item._offsetTop + item._offsetHeight;
  			item._state = '';
  		}
  
  		// Force an update
  		this.update( true );
  	}
  
  	/**
  	 * Apply past/future classes to list items outside of the viewport
  	 */
  	List.prototype.update = function( force ) {
  		var scrollTop = this.element.pageYOffset || this.element.scrollTop,
  			scrollBottom = scrollTop + this.listHeight;
  
  		// Quit if nothing changed
  		if( scrollTop !== this.lastTop || force ) {
  			this.lastTop = scrollTop;
  
  			// One loop to make our changes to the DOM
  			for( var i = 0, len = this.items.length; i < len; i++ ) {
  				var item = this.items[i];
  
  				// Above list viewport
  				if( item._offsetBottom < scrollTop ) {
  					// Exclusion via string matching improves performance
  					if( item._state !== 'past' ) {
  						item._state = 'past';
  						item.classList.add( 'past' );
  						item.classList.remove( 'future' );
  					}
  				}
  				// Below list viewport
  				else if( item._offsetTop > scrollBottom ) {
  					// Exclusion via string matching improves performance
  					if( item._state !== 'future' ) {
  						item._state = 'future';
  						item.classList.add( 'future' );
  						item.classList.remove( 'past' );
  					}
  				}
  				// Inside of list viewport
  				else if( item._state ) {
  					if( item._state === 'past' ) item.classList.remove( 'past' );
  					if( item._state === 'future' ) item.classList.remove( 'future' );
  					item._state = '';
  				}
  			}
  		}
  	}
  
  	/**
  	 * Cleans up after this list and disposes of it.
  	 */
  	List.prototype.destroy = function() {
  		clearInterval( this.syncInterval );
  
  		for( var j = 0, len = this.items.length; j < len; j++ ) {
  			var item = this.items[j];
  
  			item.classList.remove( 'past' );
  			item.classList.remove( 'future' );
  		}
  	}
  
  
  	/**
  	 * A list specifically for touch devices. Simulates the style
  	 * of scrolling you'd see on a touch device but does not rely
  	 * on webkit-overflow-scrolling since that makes it impossible
  	 * to read the up-to-date scroll position.
  	 */
  	function TouchList( element ) {
  		this.element = element;
  		this.element.style.overflow = 'hidden';
  
  		this.top = {
  			value: 0,
  			natural: 0
  		};
  
  		this.touch = {
  			value: 0,
  			offset: 0,
  			start: 0,
  			previous: 0,
  			lastMove: Date.now(),
  			accellerateTimeout: -1,
  			isAccellerating: false,
  			isActive: false
  		};
  
  		this.velocity = 0;
  	}
  	TouchList.prototype = new List();
  
  	/**
  	 * Fetches the latest properties from the DOM to ensure that
  	 * this list is in sync with its contents. This is typically
  	 * only used once (per list) at initialization.
  	 */
  	TouchList.prototype.sync = function() {
  		this.items = Array.prototype.slice.apply( this.element.children );
  
  		this.listHeight = this.element.offsetHeight;
  
  		var item;
  
  		// One loop to get the properties we need from the DOM
  		for( var i = 0, len = this.items.length; i < len; i++ ) {
  			item = this.items[i];
  			item._offsetHeight = item.offsetHeight;
  			item._offsetTop = item.offsetTop;
  			item._offsetBottom = item._offsetTop + item._offsetHeight;
  			item._state = '';
  
  			// Animating opacity is a MAJOR performance hit on mobile so we can't allow it
  			item.style.opacity = 1;
  		}
  
  		this.top.natural = this.element.scrollTop;
  		this.top.value = this.top.natural;
  		this.top.max = item._offsetBottom - this.listHeight;
  
  		// Force an update
  		this.update( true );
  
  		this.bind();
  	}
  
  	/**
  	 * Binds the events for this list. References to proxy methods
  	 * are kept for unbinding if the list is disposed of.
  	 */
  	TouchList.prototype.bind = function() {
  		var scope = this;
  
  		this.touchStartDelegate = function( event ) {
  			scope.onTouchStart( event );
  		};
  
  		this.touchMoveDelegate = function( event ) {
  			scope.onTouchMove( event );
  		};
  
  		this.touchEndDelegate = function( event ) {
  			scope.onTouchEnd( event );
  		};
  
  		this.element.addEventListener( 'touchstart', this.touchStartDelegate, false );
  		this.element.addEventListener( 'touchmove', this.touchMoveDelegate, false );
  		this.element.addEventListener( 'touchend', this.touchEndDelegate, false );
  	}
  
  	TouchList.prototype.onTouchStart = function( event ) {
  		event.preventDefault();
  
  		if( event.touches.length === 1 ) {
  			this.touch.isActive = true;
  			this.touch.start = event.touches[0].clientY;
  			this.touch.previous = this.touch.start;
  			this.touch.value = this.touch.start;
  			this.touch.offset = 0;
  
  			if( this.velocity ) {
  				this.touch.isAccellerating = true;
  
  				var scope = this;
  
  				this.touch.accellerateTimeout = setTimeout( function() {
  					scope.touch.isAccellerating = false;
  					scope.velocity = 0;
  				}, 500 );
  			}
  			else {
  				this.velocity = 0;
  			}
  		}
  	}
  
  	TouchList.prototype.onTouchMove = function( event ) {
  		if( event.touches.length === 1 ) {
  			var previous = this.touch.value;
  
  			this.touch.value = event.touches[0].clientY;
  			this.touch.lastMove = Date.now();
  
  			var sameDirection = ( this.touch.value > this.touch.previous && this.velocity < 0 )
  								|| ( this.touch.value < this.touch.previous && this.velocity > 0 );
  
  			if( this.touch.isAccellerating && sameDirection ) {
  				clearInterval( this.touch.accellerateTimeout );
  
  				// Increase velocity significantly
  				this.velocity += ( this.touch.previous - this.touch.value ) / 10;
  			}
  			else {
  				this.velocity = 0;
  
  				this.touch.isAccellerating = false;
  				this.touch.offset = Math.round( this.touch.start - this.touch.value );
  			}
  
  			this.touch.previous = previous;
  		}
  	}
  
  	TouchList.prototype.onTouchEnd = function( event ) {
  		var distanceMoved = this.touch.start - this.touch.value;
  
  		if( !this.touch.isAccellerating ) {
  			// Apply velocity based on the start position of the touch
  			this.velocity = ( this.touch.start - this.touch.value ) / 10;
  		}
  
  		// Don't apply any velocity if the touch ended in a still state
  		if( Date.now() - this.touch.lastMove > 200 || Math.abs( this.touch.previous - this.touch.value ) < 5 ) {
  			this.velocity = 0;
  		}
  
  		this.top.value += this.touch.offset;
  
  		// Reset the variables used to determne swipe speed
  		this.touch.offset = 0;
  		this.touch.start = 0;
  		this.touch.value = 0;
  		this.touch.isActive = false;
  		this.touch.isAccellerating = false;
  
  		clearInterval( this.touch.accellerateTimeout );
  
  		// If a swipe was captured, prevent event propagation
  		if( Math.abs( this.velocity ) > 4 || Math.abs( distanceMoved ) > 10 ) {
  			event.preventDefault();
  		}
  	};
  
  	/**
  	 * Apply past/future classes to list items outside of the viewport
  	 */
  	TouchList.prototype.update = function( force ) {
  		// Determine the desired scroll top position
  		var scrollTop = this.top.value + this.velocity + this.touch.offset;
  
  		// Only scroll the list if there's input
  		if( this.velocity || this.touch.offset ) {
  			// Scroll the DOM and add on the offset from touch
  			this.element.scrollTop = scrollTop;
  
  			// Keep the scroll value within bounds
  			scrollTop = Math.max( 0, Math.min( this.element.scrollTop, this.top.max ) );
  
  			// Cache the currently set scroll top and touch offset
  			this.top.value = scrollTop - this.touch.offset;
  		}
  
  		// If there is no active touch, decay velocity
  		if( !this.touch.isActive || this.touch.isAccellerating ) {
  			this.velocity *= 0.95;
  		}
  
  		// Cut off early, the last fraction of velocity doesn't have
  		// much impact on movement
  		if( Math.abs( this.velocity ) < 0.15 ) {
  			this.velocity = 0;
  		}
  
  		// Only proceed if the scroll position has changed
  		if( scrollTop !== this.top.natural || force ) {
  			this.top.natural = scrollTop;
  			this.top.value = scrollTop - this.touch.offset;
  
  			var scrollBottom = scrollTop + this.listHeight;
  
  			// One loop to make our changes to the DOM
  			for( var i = 0, len = this.items.length; i < len; i++ ) {
  				var item = this.items[i];
  
  				// Above list viewport
  				if( item._offsetBottom < scrollTop ) {
  					// Exclusion via string matching improves performance
  					if( this.velocity <= 0 && item._state !== 'past' ) {
  						item.classList.add( 'past' );
  						item._state = 'past';
  					}
  				}
  				// Below list viewport
  				else if( item._offsetTop > scrollBottom ) {
  					// Exclusion via string matching improves performance
  					if( this.velocity >= 0 && item._state !== 'future' ) {
  						item.classList.add( 'future' );
  						item._state = 'future';
  					}
  				}
  				// Inside of list viewport
  				else if( item._state ) {
  					if( item._state === 'past' ) item.classList.remove( 'past' );
  					if( item._state === 'future' ) item.classList.remove( 'future' );
  					item._state = '';
  				}
  			}
  		}
  	};
  
  	/**
  	 * Cleans up after this list and disposes of it.
  	 */
  	TouchList.prototype.destroy = function() {
  		List.prototype.destroy.apply( this );
  
  		this.element.removeEventListener( 'touchstart', this.touchStartDelegate, false );
  		this.element.removeEventListener( 'touchmove', this.touchMoveDelegate, false );
  		this.element.removeEventListener( 'touchend', this.touchEndDelegate, false );
  	}
  
  
  	/**
  	 * Public API
  	 */
  	window.stroll = {
  		/**
  		 * Binds one or more lists for scroll effects.
  		 *
  		 * @see #add()
  		 */
  		bind: function( target, options ) {
  			if( isCapable() ) {
  				batch( target, add, options );
  			}
  		},
  
  		/**
  		 * Unbinds one or more lists from scroll effects.
  		 *
  		 * @see #remove()
  		 */
  		unbind: function( target ) {
  			if( isCapable() ) {
  				batch( target, remove );
  			}
  		}
  	}
  
  	window.requestAnimFrame = (function(){
  		return  window.requestAnimationFrame       ||
  				window.webkitRequestAnimationFrame ||
  				window.mozRequestAnimationFrame    ||
  				window.oRequestAnimationFrame      ||
  				window.msRequestAnimationFrame     ||
  				function( callback ){
  					window.setTimeout(callback, 1000 / 60);
  				};
  	})()
  
  })();
  