text: (function(global) {
    var time = Date.now || function() {
      return +new Date();
    };
    var desiredFrames = 60;
    var millisecondsPerSecond = 1000;
    var running = {};
    var counter = 1;
  
    // Create namespaces
    if (!global.core) {
      global.core = { effect : {} };
    } else if (!core.effect) {
      core.effect = {};
    }
  
    core.effect.Animate = {
  
      /**
       * A requestAnimationFrame wrapper / polyfill.
       *
       * @param callback {Function} The callback to be invoked before the next repaint.
       * @param root {HTMLElement} The root element for the repaint
       */
      requestAnimationFrame: (function() {
  
        // Check for request animation Frame support
        var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
        var isNative = !!requestFrame;
  
        if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
          isNative = false;
        }
  
        if (isNative) {
          return function(callback, root) {
            requestFrame(callback, root)
          };
        }
  
        var TARGET_FPS = 60;
        var requests = {};
        var requestCount = 0;
        var rafHandle = 1;
        var intervalHandle = null;
        var lastActive = +new Date();
  
        return function(callback, root) {
          var callbackHandle = rafHandle++;
  
          // Store callback
          requests[callbackHandle] = callback;
          requestCount++;
  
          // Create timeout at first request
          if (intervalHandle === null) {
  
            intervalHandle = setInterval(function() {
  
              var time = +new Date();
              var currentRequests = requests;
  
              // Reset data structure before executing callbacks
              requests = {};
              requestCount = 0;
  
              for(var key in currentRequests) {
                if (currentRequests.hasOwnProperty(key)) {
                  currentRequests[key](time);
                  lastActive = time;
                }
              }
  
              // Disable the timeout when nothing happens for a certain
              // period of time
              if (time - lastActive > 2500) {
                clearInterval(intervalHandle);
                intervalHandle = null;
              }
  
            }, 1000 / TARGET_FPS);
          }
  
          return callbackHandle;
        };
  
      })(),
  
  
      /**
       * Stops the given animation.
       *
       * @param id {Integer} Unique animation ID
       * @return {Boolean} Whether the animation was stopped (aka, was running before)
       */
      stop: function(id) {
        var cleared = running[id] != null;
        if (cleared) {
          running[id] = null;
        }
  
        return cleared;
      },
  
  
      /**
       * Whether the given animation is still running.
       *
       * @param id {Integer} Unique animation ID
       * @return {Boolean} Whether the animation is still running
       */
      isRunning: function(id) {
        return running[id] != null;
      },
  
  
      /**
       * Start the animation.
       *
       * @param stepCallback {Function} Pointer to function which is executed on every step.
       *   Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
       * @param verifyCallback {Function} Executed before every animation step.
       *   Signature of the method should be `function() { return continueWithAnimation; }`
       * @param completedCallback {Function}
       *   Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
       * @param duration {Integer} Milliseconds to run the animation
       * @param easingMethod {Function} Pointer to easing function
       *   Signature of the method should be `function(percent) { return modifiedValue; }`
       * @param root {Element ? document.body} Render root, when available. Used for internal
       *   usage of requestAnimationFrame.
       * @return {Integer} Identifier of animation. Can be used to stop it any time.
       */
      start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
  
        var start = time();
        var lastFrame = start;
        var percent = 0;
        var dropCounter = 0;
        var id = counter++;
  
        if (!root) {
          root = document.body;
        }
  
        // Compacting running db automatically every few new animations
        if (id % 20 === 0) {
          var newRunning = {};
          for (var usedId in running) {
            newRunning[usedId] = true;
          }
          running = newRunning;
        }
  
        // This is the internal step method which is called every few milliseconds
        var step = function(virtual) {
  
          // Normalize virtual value
          var render = virtual !== true;
  
          // Get current time
          var now = time();
  
          // Verification is executed before next animation step
          if (!running[id] || (verifyCallback && !verifyCallback(id))) {
  
            running[id] = null;
            completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
            return;
  
          }
  
          // For the current rendering to apply let's update omitted steps in memory.
          // This is important to bring internal state variables up-to-date with progress in time.
          if (render) {
  
            var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
            for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
              step(true);
              dropCounter++;
            }
  
          }
  
          // Compute percent value
          if (duration) {
            percent = (now - start) / duration;
            if (percent > 1) {
              percent = 1;
            }
          }
  
          // Execute step callback, then...
          var value = easingMethod ? easingMethod(percent) : percent;
          if ((stepCallback(value, now, render) === false || percent === 1) && render) {
            running[id] = null;
            completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);
          } else if (render) {
            lastFrame = now;
            core.effect.Animate.requestAnimationFrame(step, root);
          }
        };
  
        // Mark as running
        running[id] = true;
  
        // Init first step
        core.effect.Animate.requestAnimationFrame(step, root);
  
        // Return unique animation ID
        return id;
      }
    };
  })(window);
  
  var Scroller;
  
  (function(window) {
  	var NOOP = function(){};
  
  	// var core;
  
  	/**
  	 * A pure logic 'component' for 'virtual' scrolling/zooming.
  	 */
  	Scroller = function(callback, options) {
  
  		this.__callback = callback;
      // core = animate;
  
  		this.options = {
  
  			/** Enable scrolling on x-axis */
  			scrollingX: true,
  
  			/** Enable scrolling on y-axis */
  			scrollingY: true,
  
  			/** Enable animations for deceleration, snap back, zooming and scrolling */
  			animating: true,
  
  			/** duration for animations triggered by scrollTo/zoomTo */
  			animationDuration: 250,
  
  			/** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */
  			bouncing: true,
  
  			/** Enable locking to the main axis if user moves only slightly on one of them at start */
  			locking: true,
  
  			/** Enable pagination mode (switching between full page content panes) */
  			paging: false,
  
  			/** Enable snapping of content to a configured pixel grid */
  			snapping: false,
  
  			/** Enable zooming of content via API, fingers and mouse wheel */
  			zooming: false,
  
  			/** Minimum zoom level */
  			minZoom: 0.5,
  
  			/** Maximum zoom level */
  			maxZoom: 3,
  
  			/** Multiply or decrease scrolling speed **/
  			speedMultiplier: 1,
  
  			/** Callback that is fired on the later of touch end or deceleration end,
  				provided that another scrolling action has not begun. Used to know
  				when to fade out a scrollbar. */
  			scrollingComplete: NOOP,
  
  			/** This configures the amount of change applied to deceleration when reaching boundaries  **/
        penetrationDeceleration : 0.03,
  
        /** This configures the amount of change applied to acceleration when reaching boundaries  **/
        penetrationAcceleration : 0.08
  
  		};
  
  		for (var key in options) {
  			this.options[key] = options[key];
  		}
  
  	};
  
  
  	// Easing Equations (c) 2003 Robert Penner, all rights reserved.
  	// Open source under the BSD License.
  
  	/**
  	 * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
  	**/
  	var easeOutCubic = function(pos) {
  		return (Math.pow((pos - 1), 3) + 1);
  	};
  
  	/**
  	 * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
  	**/
  	var easeInOutCubic = function(pos) {
  		if ((pos /= 0.5) < 1) {
  			return 0.5 * Math.pow(pos, 3);
  		}
  
  		return 0.5 * (Math.pow((pos - 2), 3) + 2);
  	};
  
  
  	var members = {
  
  		/*
  		---------------------------------------------------------------------------
  			INTERNAL FIELDS :: STATUS
  		---------------------------------------------------------------------------
  		*/
  
  		/** {Boolean} Whether only a single finger is used in touch handling */
  		__isSingleTouch: false,
  
  		/** {Boolean} Whether a touch event sequence is in progress */
  		__isTracking: false,
  
  		/** {Boolean} Whether a deceleration animation went to completion. */
  		__didDecelerationComplete: false,
  
  		/**
  		 * {Boolean} Whether a gesture zoom/rotate event is in progress. Activates when
  		 * a gesturestart event happens. This has higher priority than dragging.
  		 */
  		__isGesturing: false,
  
  		/**
  		 * {Boolean} Whether the user has moved by such a distance that we have enabled
  		 * dragging mode. Hint: It's only enabled after some pixels of movement to
  		 * not interrupt with clicks etc.
  		 */
  		__isDragging: false,
  
  		/**
  		 * {Boolean} Not touching and dragging anymore, and smoothly animating the
  		 * touch sequence using deceleration.
  		 */
  		__isDecelerating: false,
  
  		/**
  		 * {Boolean} Smoothly animating the currently configured change
  		 */
  		__isAnimating: false,
  
  
  
  		/*
  		---------------------------------------------------------------------------
  			INTERNAL FIELDS :: DIMENSIONS
  		---------------------------------------------------------------------------
  		*/
  
  		/** {Integer} Available outer left position (from document perspective) */
  		__clientLeft: 0,
  
  		/** {Integer} Available outer top position (from document perspective) */
  		__clientTop: 0,
  
  		/** {Integer} Available outer width */
  		__clientWidth: 0,
  
  		/** {Integer} Available outer height */
  		__clientHeight: 0,
  
  		/** {Integer} Outer width of content */
  		__contentWidth: 0,
  
  		/** {Integer} Outer height of content */
  		__contentHeight: 0,
  
  		/** {Integer} Snapping width for content */
  		__snapWidth: 100,
  
  		/** {Integer} Snapping height for content */
  		__snapHeight: 100,
  
  		/** {Integer} Height to assign to refresh area */
  		__refreshHeight: null,
  
  		/** {Boolean} Whether the refresh process is enabled when the event is released now */
  		__refreshActive: false,
  
  		/** {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */
  		__refreshActivate: null,
  
  		/** {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */
  		__refreshDeactivate: null,
  
  		/** {Function} Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
  		__refreshStart: null,
  
  		/** {Number} Zoom level */
  		__zoomLevel: 1,
  
  		/** {Number} Scroll position on x-axis */
  		__scrollLeft: 0,
  
  		/** {Number} Scroll position on y-axis */
  		__scrollTop: 0,
  
  		/** {Integer} Maximum allowed scroll position on x-axis */
  		__maxScrollLeft: 0,
  
  		/** {Integer} Maximum allowed scroll position on y-axis */
  		__maxScrollTop: 0,
  
  		/* {Number} Scheduled left position (final position when animating) */
  		__scheduledLeft: 0,
  
  		/* {Number} Scheduled top position (final position when animating) */
  		__scheduledTop: 0,
  
  		/* {Number} Scheduled zoom level (final scale when animating) */
  		__scheduledZoom: 0,
  
  
  
  		/*
  		---------------------------------------------------------------------------
  			INTERNAL FIELDS :: LAST POSITIONS
  		---------------------------------------------------------------------------
  		*/
  
  		/** {Number} Left position of finger at start */
  		__lastTouchLeft: null,
  
  		/** {Number} Top position of finger at start */
  		__lastTouchTop: null,
  
  		/** {Date} Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */
  		__lastTouchMove: null,
  
  		/** {Array} List of positions, uses three indexes for each state: left, top, timestamp */
  		__positions: null,
  
  
  
  		/*
  		---------------------------------------------------------------------------
  			INTERNAL FIELDS :: DECELERATION SUPPORT
  		---------------------------------------------------------------------------
  		*/
  
  		/** {Integer} Minimum left scroll position during deceleration */
  		__minDecelerationScrollLeft: null,
  
  		/** {Integer} Minimum top scroll position during deceleration */
  		__minDecelerationScrollTop: null,
  
  		/** {Integer} Maximum left scroll position during deceleration */
  		__maxDecelerationScrollLeft: null,
  
  		/** {Integer} Maximum top scroll position during deceleration */
  		__maxDecelerationScrollTop: null,
  
  		/** {Number} Current factor to modify horizontal scroll position with on every step */
  		__decelerationVelocityX: null,
  
  		/** {Number} Current factor to modify vertical scroll position with on every step */
  		__decelerationVelocityY: null,
  
  
  
  		/*
  		---------------------------------------------------------------------------
  			PUBLIC API
  		---------------------------------------------------------------------------
  		*/
  
  		/**
  		 * Configures the dimensions of the client (outer) and content (inner) elements.
  		 * Requires the available space for the outer element and the outer size of the inner element.
  		 * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
  		 *
  		 * @param clientWidth {Integer ? null} Inner width of outer element
  		 * @param clientHeight {Integer ? null} Inner height of outer element
  		 * @param contentWidth {Integer ? null} Outer width of inner element
  		 * @param contentHeight {Integer ? null} Outer height of inner element
  		 */
  		setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {
  
  			var self = this;
  
  			// Only update values which are defined
  			if (clientWidth === +clientWidth) {
  				self.__clientWidth = clientWidth;
  			}
  
  			if (clientHeight === +clientHeight) {
  				self.__clientHeight = clientHeight;
  			}
  
  			if (contentWidth === +contentWidth) {
  				self.__contentWidth = contentWidth;
  			}
  
  			if (contentHeight === +contentHeight) {
  				self.__contentHeight = contentHeight;
  			}
  
  			// Refresh maximums
  			self.__computeScrollMax();
  
  			// Refresh scroll position
  			self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
  
  		},
  
  
  		/**
  		 * Sets the client coordinates in relation to the document.
  		 *
  		 * @param left {Integer ? 0} Left position of outer element
  		 * @param top {Integer ? 0} Top position of outer element
  		 */
  		setPosition: function(left, top) {
  
  			var self = this;
  
  			self.__clientLeft = left || 0;
  			self.__clientTop = top || 0;
  
  		},
  
  
  		/**
  		 * Configures the snapping (when snapping is active)
  		 *
  		 * @param width {Integer} Snapping width
  		 * @param height {Integer} Snapping height
  		 */
  		setSnapSize: function(width, height) {
  
  			var self = this;
  
  			self.__snapWidth = width;
  			self.__snapHeight = height;
  
  		},
  
  
  		/**
  		 * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever
  		 * the user event is released during visibility of this zone. This was introduced by some apps on iOS like
  		 * the official Twitter client.
  		 *
  		 * @param height {Integer} Height of pull-to-refresh zone on top of rendered list
  		 * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.
  		 * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.
  		 * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
  		 */
  		activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback) {
  
  			var self = this;
  
  			self.__refreshHeight = height;
  			self.__refreshActivate = activateCallback;
  			self.__refreshDeactivate = deactivateCallback;
  			self.__refreshStart = startCallback;
  
  		},
  
  
  		/**
  		 * Starts pull-to-refresh manually.
  		 */
  		triggerPullToRefresh: function() {
  			// Use publish instead of scrollTo to allow scrolling to out of boundary position
  			// We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
  			this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true);
  
  			if (this.__refreshStart) {
  				this.__refreshStart();
  			}
  		},
  
  
  		/**
  		 * Signalizes that pull-to-refresh is finished.
  		 */
  		finishPullToRefresh: function() {
  
  			var self = this;
  
  			self.__refreshActive = false;
  			if (self.__refreshDeactivate) {
  				self.__refreshDeactivate();
  			}
  
  			self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
  
  		},
  
  
  		/**
  		 * Returns the scroll position and zooming values
  		 *
  		 * @return {Map} `left` and `top` scroll position and `zoom` level
  		 */
  		getValues: function() {
  
  			var self = this;
  
  			return {
  				left: self.__scrollLeft,
  				top: self.__scrollTop,
  				zoom: self.__zoomLevel
  			};
  
  		},
  
  
  		/**
  		 * Returns the maximum scroll values
  		 *
  		 * @return {Map} `left` and `top` maximum scroll values
  		 */
  		getScrollMax: function() {
  
  			var self = this;
  
  			return {
  				left: self.__maxScrollLeft,
  				top: self.__maxScrollTop
  			};
  
  		},
  
  
  		/**
  		 * Zooms to the given level. Supports optional animation. Zooms
  		 * the center when no coordinates are given.
  		 *
  		 * @param level {Number} Level to zoom to
  		 * @param animate {Boolean ? false} Whether to use animation
  		 * @param originLeft {Number ? null} Zoom in at given left coordinate
  		 * @param originTop {Number ? null} Zoom in at given top coordinate
  		 * @param callback {Function ? null} A callback that gets fired when the zoom is complete.
  		 */
  		zoomTo: function(level, animate, originLeft, originTop, callback) {
  
  			var self = this;
  
  			if (!self.options.zooming) {
  				throw new Error("Zooming is not enabled!");
  			}
  
  			// Add callback if exists
  			if(callback) {
  				self.__zoomComplete = callback;
  			}
  
  			// Stop deceleration
  			if (self.__isDecelerating) {
  				core.effect.Animate.stop(self.__isDecelerating);
  				self.__isDecelerating = false;
  			}
  
  			var oldLevel = self.__zoomLevel;
  
  			// Normalize input origin to center of viewport if not defined
  			if (originLeft == null) {
  				originLeft = self.__clientWidth / 2;
  			}
  
  			if (originTop == null) {
  				originTop = self.__clientHeight / 2;
  			}
  
  			// Limit level according to configuration
  			level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
  
  			// Recompute maximum values while temporary tweaking maximum scroll ranges
  			self.__computeScrollMax(level);
  
  			// Recompute left and top coordinates based on new zoom level
  			var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;
  			var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;
  
  			// Limit x-axis
  			if (left > self.__maxScrollLeft) {
  				left = self.__maxScrollLeft;
  			} else if (left < 0) {
  				left = 0;
  			}
  
  			// Limit y-axis
  			if (top > self.__maxScrollTop) {
  				top = self.__maxScrollTop;
  			} else if (top < 0) {
  				top = 0;
  			}
  
  			// Push values out
  			self.__publish(left, top, level, animate);
  
  		},
  
  
  		/**
  		 * Zooms the content by the given factor.
  		 *
  		 * @param factor {Number} Zoom by given factor
  		 * @param animate {Boolean ? false} Whether to use animation
  		 * @param originLeft {Number ? 0} Zoom in at given left coordinate
  		 * @param originTop {Number ? 0} Zoom in at given top coordinate
  		 * @param callback {Function ? null} A callback that gets fired when the zoom is complete.
  		 */
  		zoomBy: function(factor, animate, originLeft, originTop, callback) {
  
  			var self = this;
  
  			self.zoomTo(self.__zoomLevel * factor, animate, originLeft, originTop, callback);
  
  		},
  
  
  		/**
  		 * Scrolls to the given position. Respect limitations and snapping automatically.
  		 *
  		 * @param left {Number?null} Horizontal scroll position, keeps current if value is <code>null</code>
  		 * @param top {Number?null} Vertical scroll position, keeps current if value is <code>null</code>
  		 * @param animate {Boolean?false} Whether the scrolling should happen using an animation
  		 * @param zoom {Number?null} Zoom level to go to
  		 */
  		scrollTo: function(left, top, animate, zoom) {
  
  			var self = this;
  
  			// Stop deceleration
  			if (self.__isDecelerating) {
  				core.effect.Animate.stop(self.__isDecelerating);
  				self.__isDecelerating = false;
  			}
  
  			// Correct coordinates based on new zoom level
  			if (zoom != null && zoom !== self.__zoomLevel) {
  
  				if (!self.options.zooming) {
  					throw new Error("Zooming is not enabled!");
  				}
  
  				left *= zoom;
  				top *= zoom;
  
  				// Recompute maximum values while temporary tweaking maximum scroll ranges
  				self.__computeScrollMax(zoom);
  
  			} else {
  
  				// Keep zoom when not defined
  				zoom = self.__zoomLevel;
  
  			}
  
  			if (!self.options.scrollingX) {
  
  				left = self.__scrollLeft;
  
  			} else {
  
  				if (self.options.paging) {
  					left = Math.round(left / self.__clientWidth) * self.__clientWidth;
  				} else if (self.options.snapping) {
  					left = Math.round(left / self.__snapWidth) * self.__snapWidth;
  				}
  
  			}
  
  			if (!self.options.scrollingY) {
  
  				top = self.__scrollTop;
  
  			} else {
  
  				if (self.options.paging) {
  					top = Math.round(top / self.__clientHeight) * self.__clientHeight;
  				} else if (self.options.snapping) {
  					top = Math.round(top / self.__snapHeight) * self.__snapHeight;
  				}
  
  			}
  
  			// Limit for allowed ranges
  			left = Math.max(Math.min(self.__maxScrollLeft, left), 0);
  			top = Math.max(Math.min(self.__maxScrollTop, top), 0);
  
  			// Don't animate when no change detected, still call publish to make sure
  			// that rendered position is really in-sync with internal data
  			if (left === self.__scrollLeft && top === self.__scrollTop) {
  				animate = false;
  			}
  
  			// Publish new values
  			if (!self.__isTracking) {
          self.__publish(left, top, zoom, animate);
        }
  
  		},
  
  
  		/**
  		 * Scroll by the given offset
  		 *
  		 * @param left {Number ? 0} Scroll x-axis by given offset
  		 * @param top {Number ? 0} Scroll x-axis by given offset
  		 * @param animate {Boolean ? false} Whether to animate the given change
  		 */
  		scrollBy: function(left, top, animate) {
  
  			var self = this;
  
  			var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
  			var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
  
  			self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
  
  		},
  
  
  
  		/*
  		---------------------------------------------------------------------------
  			EVENT CALLBACKS
  		---------------------------------------------------------------------------
  		*/
  
  		/**
  		 * Mouse wheel handler for zooming support
  		 */
  		doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {
  
  			var self = this;
  			var change = wheelDelta > 0 ? 0.97 : 1.03;
  
  			return self.zoomTo(self.__zoomLevel * change, false, pageX - self.__clientLeft, pageY - self.__clientTop);
  
  		},
  
  
  		/**
  		 * Touch start handler for scrolling support
  		 */
  		doTouchStart: function(touches, timeStamp) {
  			// Array-like check is enough here
  			if (touches.length == null) {
  				throw new Error("Invalid touch list: " + touches);
  			}
  
  			if (timeStamp instanceof Date) {
  				timeStamp = timeStamp.valueOf();
  			}
  			if (typeof timeStamp !== "number") {
  				throw new Error("Invalid timestamp value: " + timeStamp);
  			}
  
  			var self = this;
  
  			// Reset interruptedAnimation flag
  			self.__interruptedAnimation = true;
  
  			// Stop deceleration
  			if (self.__isDecelerating) {
  				core.effect.Animate.stop(self.__isDecelerating);
  				self.__isDecelerating = false;
  				self.__interruptedAnimation = true;
  			}
  
  			// Stop animation
  			if (self.__isAnimating) {
          core.effect.Animate.stop(self.__isAnimating);
  				self.__isAnimating = false;
  				self.__interruptedAnimation = true;
  			}
  
  			// Use center point when dealing with two fingers
  			var currentTouchLeft, currentTouchTop;
  			var isSingleTouch = touches.length === 1;
  			if (isSingleTouch) {
  				currentTouchLeft = touches[0].pageX;
  				currentTouchTop = touches[0].pageY;
  			} else {
  				currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
  				currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
  			}
  
  			// Store initial positions
  			self.__initialTouchLeft = currentTouchLeft;
  			self.__initialTouchTop = currentTouchTop;
  
  			// Store current zoom level
  			self.__zoomLevelStart = self.__zoomLevel;
  
  			// Store initial touch positions
  			self.__lastTouchLeft = currentTouchLeft;
  			self.__lastTouchTop = currentTouchTop;
  
  			// Store initial move time stamp
  			self.__lastTouchMove = timeStamp;
  
  			// Reset initial scale
  			self.__lastScale = 1;
  
  			// Reset locking flags
  			self.__enableScrollX = !isSingleTouch && self.options.scrollingX;
  			self.__enableScrollY = !isSingleTouch && self.options.scrollingY;
  
  			// Reset tracking flag
  			self.__isTracking = true;
  
  			// Reset deceleration complete flag
  			self.__didDecelerationComplete = false;
  
  			// Dragging starts directly with two fingers, otherwise lazy with an offset
  			self.__isDragging = !isSingleTouch;
  
  			// Some features are disabled in multi touch scenarios
  			self.__isSingleTouch = isSingleTouch;
  
  			// Clearing data structure
  			self.__positions = [];
  
  		},
  
  
  		/**
  		 * Touch move handler for scrolling support
  		 */
  		doTouchMove: function(touches, timeStamp, scale) {
  
  			// Array-like check is enough here
  			if (touches.length == null) {
  				throw new Error("Invalid touch list: " + touches);
  			}
  
  			if (timeStamp instanceof Date) {
  				timeStamp = timeStamp.valueOf();
  			}
  			if (typeof timeStamp !== "number") {
  				throw new Error("Invalid timestamp value: " + timeStamp);
  			}
  
  			var self = this;
  
  			// Ignore event when tracking is not enabled (event might be outside of element)
  			if (!self.__isTracking) {
  				return;
  			}
  
  
  			var currentTouchLeft, currentTouchTop;
  
  			// Compute move based around of center of fingers
  			if (touches.length === 2) {
  				currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
  				currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
  			} else {
  				currentTouchLeft = touches[0].pageX;
  				currentTouchTop = touches[0].pageY;
  			}
  
  			var positions = self.__positions;
  
  			// Are we already is dragging mode?
  			if (self.__isDragging) {
  
  				// Compute move distance
  				var moveX = currentTouchLeft - self.__lastTouchLeft;
  				var moveY = currentTouchTop - self.__lastTouchTop;
  
  				// Read previous scroll position and zooming
  				var scrollLeft = self.__scrollLeft;
  				var scrollTop = self.__scrollTop;
  				var level = self.__zoomLevel;
  
  				// Work with scaling
  				if (scale != null && self.options.zooming) {
  
  					var oldLevel = level;
  
  					// Recompute level based on previous scale and new scale
  					level = level / self.__lastScale * scale;
  
  					// Limit level according to configuration
  					level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
  
  					// Only do further compution when change happened
  					if (oldLevel !== level) {
  
  						// Compute relative event position to container
  						var currentTouchLeftRel = currentTouchLeft - self.__clientLeft;
  						var currentTouchTopRel = currentTouchTop - self.__clientTop;
  
  						// Recompute left and top coordinates based on new zoom level
  						scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;
  						scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;
  
  						// Recompute max scroll values
  						self.__computeScrollMax(level);
  
  					}
  				}
  
  				if (self.__enableScrollX) {
  
  					scrollLeft -= moveX * this.options.speedMultiplier;
  					var maxScrollLeft = self.__maxScrollLeft;
  
  					if (scrollLeft > maxScrollLeft || scrollLeft < 0) {
  
  						// Slow down on the edges
  						if (self.options.bouncing) {
  
  							scrollLeft += (moveX / 2  * this.options.speedMultiplier);
  
  						} else if (scrollLeft > maxScrollLeft) {
  
  							scrollLeft = maxScrollLeft;
  
  						} else {
  
  							scrollLeft = 0;
  
  						}
  					}
  				}
  
  				// Compute new vertical scroll position
  				if (self.__enableScrollY) {
  
  					scrollTop -= moveY * this.options.speedMultiplier;
  					var maxScrollTop = self.__maxScrollTop;
  
  					if (scrollTop > maxScrollTop || scrollTop < 0) {
  
  						// Slow down on the edges
  						if (self.options.bouncing) {
  
  							scrollTop += (moveY / 2 * this.options.speedMultiplier);
  
  							// Support pull-to-refresh (only when only y is scrollable)
  							if (!self.__enableScrollX && self.__refreshHeight != null) {
  
  								if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {
  
  									self.__refreshActive = true;
  									if (self.__refreshActivate) {
  										self.__refreshActivate();
  									}
  
  								} else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {
  
  									self.__refreshActive = false;
  									if (self.__refreshDeactivate) {
  										self.__refreshDeactivate();
  									}
  
  								}
  							}
  
  						} else if (scrollTop > maxScrollTop) {
  
  							scrollTop = maxScrollTop;
  
  						} else {
  
  							scrollTop = 0;
  
  						}
  					}
  				}
  
  				// Keep list from growing infinitely (holding min 10, max 20 measure points)
  				if (positions.length > 60) {
  					positions.splice(0, 30);
  				}
  
  				// Track scroll movement for decleration
  				positions.push(scrollLeft, scrollTop, timeStamp);
  
  				// Sync scroll position
  				self.__publish(scrollLeft, scrollTop, level);
  
  			// Otherwise figure out whether we are switching into dragging mode now.
  			} else {
  
  				var minimumTrackingForScroll = self.options.locking ? 3 : 0;
  				var minimumTrackingForDrag = 5;
  
  				var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);
  				var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);
  
  				self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;
  				self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;
  
  				positions.push(self.__scrollLeft, self.__scrollTop, timeStamp);
  
  				self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);
  				if (self.__isDragging) {
  					self.__interruptedAnimation = false;
  				}
  
  			}
  
  			// Update last touch positions and time stamp for next event
  			self.__lastTouchLeft = currentTouchLeft;
  			self.__lastTouchTop = currentTouchTop;
  			self.__lastTouchMove = timeStamp;
  			self.__lastScale = scale;
  
  		},
  
  
  		/**
  		 * Touch end handler for scrolling support
  		 */
  		doTouchEnd: function(timeStamp) {
  
  			if (timeStamp instanceof Date) {
  				timeStamp = timeStamp.valueOf();
  			}
  			if (typeof timeStamp !== "number") {
  				throw new Error("Invalid timestamp value: " + timeStamp);
  			}
  
  			var self = this;
  
  			// Ignore event when tracking is not enabled (no touchstart event on element)
  			// This is required as this listener ('touchmove') sits on the document and not on the element itself.
  			if (!self.__isTracking) {
  				return;
  			}
  
  			// Not touching anymore (when two finger hit the screen there are two touch end events)
  			self.__isTracking = false;
  
  			// Be sure to reset the dragging flag now. Here we also detect whether
  			// the finger has moved fast enough to switch into a deceleration animation.
  			if (self.__isDragging) {
  
  				// Reset dragging flag
  				self.__isDragging = false;
  
  				// Start deceleration
  				// Verify that the last move detected was in some relevant time frame
  				if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {
  
  					// Then figure out what the scroll position was about 100ms ago
  					var positions = self.__positions;
  					var endPos = positions.length - 1;
  					var startPos = endPos;
  
  					// Move pointer to position measured 100ms ago
  					for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {
  						startPos = i;
  					}
  
  					// If start and stop position is identical in a 100ms timeframe,
  					// we cannot compute any useful deceleration.
  					if (startPos !== endPos) {
  
  						// Compute relative movement between these two points
  						var timeOffset = positions[endPos] - positions[startPos];
  						var movedLeft = self.__scrollLeft - positions[startPos - 2];
  						var movedTop = self.__scrollTop - positions[startPos - 1];
  
  						// Based on 50ms compute the movement to apply for each render step
  						self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);
  						self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);
  
  						// How much velocity is required to start the deceleration
  						var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? 4 : 1;
  
  						// Verify that we have enough velocity to start deceleration
  						if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {
  
  							// Deactivate pull-to-refresh when decelerating
  							if (!self.__refreshActive) {
  								self.__startDeceleration(timeStamp);
  							}
  						} else {
  							self.options.scrollingComplete();
  						}
  					} else {
  						self.options.scrollingComplete();
  					}
  				} else if ((timeStamp - self.__lastTouchMove) > 100) {
  					self.options.scrollingComplete();
  	 			}
  			}
  
  			// If this was a slower move it is per default non decelerated, but this
  			// still means that we want snap back to the bounds which is done here.
  			// This is placed outside the condition above to improve edge case stability
  			// e.g. touchend fired without enabled dragging. This should normally do not
  			// have modified the scroll positions or even showed the scrollbars though.
  			if (!self.__isDecelerating) {
  
  				if (self.__refreshActive && self.__refreshStart) {
  
  					// Use publish instead of scrollTo to allow scrolling to out of boundary position
  					// We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
  					self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);
  
  					if (self.__refreshStart) {
  						self.__refreshStart();
  					}
  
  				} else {
  
  					if (self.__interruptedAnimation || self.__isDragging) {
  						self.options.scrollingComplete();
  					}
  					self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);
  
  					// Directly signalize deactivation (nothing todo on refresh?)
  					if (self.__refreshActive) {
  
  						self.__refreshActive = false;
  						if (self.__refreshDeactivate) {
  							self.__refreshDeactivate();
  						}
  
  					}
  				}
  			}
  
  			// Fully cleanup list
  			self.__positions.length = 0;
  
  		},
  
  
  
  		/*
  		---------------------------------------------------------------------------
  			PRIVATE API
  		---------------------------------------------------------------------------
  		*/
  
  		/**
  		 * Applies the scroll position to the content element
  		 *
  		 * @param left {Number} Left scroll position
  		 * @param top {Number} Top scroll position
  		 * @param animate {Boolean?false} Whether animation should be used to move to the new coordinates
  		 */
  		__publish: function(left, top, zoom, animate) {
  
  			var self = this;
  
  			// Remember whether we had an animation, then we try to continue based on the current "drive" of the animation
  			var wasAnimating = self.__isAnimating;
  			if (wasAnimating) {
          core.effect.Animate.stop(wasAnimating);
  				self.__isAnimating = false;
  			}
  
  			if (animate && self.options.animating) {
  
  				// Keep scheduled positions for scrollBy/zoomBy functionality
  				self.__scheduledLeft = left;
  				self.__scheduledTop = top;
  				self.__scheduledZoom = zoom;
  
  				var oldLeft = self.__scrollLeft;
  				var oldTop = self.__scrollTop;
  				var oldZoom = self.__zoomLevel;
  
  				var diffLeft = left - oldLeft;
  				var diffTop = top - oldTop;
  				var diffZoom = zoom - oldZoom;
  
  				var step = function(percent, now, render) {
  
  					if (render) {
  
  						self.__scrollLeft = oldLeft + (diffLeft * percent);
  						self.__scrollTop = oldTop + (diffTop * percent);
  						self.__zoomLevel = oldZoom + (diffZoom * percent);
  
  						// Push values out
  						if (self.__callback) {
  							self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel);
  						}
  
  					}
  				};
  
  				var verify = function(id) {
  					return self.__isAnimating === id;
  				};
  
  				var completed = function(renderedFramesPerSecond, animationId, wasFinished) {
  					if (animationId === self.__isAnimating) {
  						self.__isAnimating = false;
  					}
  					if (self.__didDecelerationComplete || wasFinished) {
  						self.options.scrollingComplete();
  					}
  
  					if (self.options.zooming) {
  						self.__computeScrollMax();
  						if(self.__zoomComplete) {
  							self.__zoomComplete();
  							self.__zoomComplete = null;
  						}
  					}
  				};
  
  				// When continuing based on previous animation we choose an ease-out animation instead of ease-in-out
          self.__isAnimating = core.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);
  
  			} else {
  
  				self.__scheduledLeft = self.__scrollLeft = left;
  				self.__scheduledTop = self.__scrollTop = top;
  				self.__scheduledZoom = self.__zoomLevel = zoom;
  
  				// Push values out
  				if (self.__callback) {
  					self.__callback(left, top, zoom);
  				}
  
  				// Fix max scroll ranges
  				if (self.options.zooming) {
  					self.__computeScrollMax();
  					if(self.__zoomComplete) {
  						self.__zoomComplete();
  						self.__zoomComplete = null;
  					}
  				}
  			}
  		},
  
  
  		/**
  		 * Recomputes scroll minimum values based on client dimensions and content dimensions.
  		 */
  		__computeScrollMax: function(zoomLevel) {
  
  			var self = this;
  
  			if (zoomLevel == null) {
  				zoomLevel = self.__zoomLevel;
  			}
  
  			self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);
  			self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);
  
  		},
  
  
  
  		/*
  		---------------------------------------------------------------------------
  			ANIMATION (DECELERATION) SUPPORT
  		---------------------------------------------------------------------------
  		*/
  
  		/**
  		 * Called when a touch sequence end and the speed of the finger was high enough
  		 * to switch into deceleration mode.
  		 */
  		__startDeceleration: function(timeStamp) {
  
  			var self = this;
  
  			if (self.options.paging) {
  
  				var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);
  				var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);
  				var clientWidth = self.__clientWidth;
  				var clientHeight = self.__clientHeight;
  
  				// We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.
  				// Each page should have exactly the size of the client area.
  				self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;
  				self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;
  				self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;
  				self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;
  
  			} else {
  
  				self.__minDecelerationScrollLeft = 0;
  				self.__minDecelerationScrollTop = 0;
  				self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
  				self.__maxDecelerationScrollTop = self.__maxScrollTop;
  
  			}
  
  			// Wrap class method
  			var step = function(percent, now, render) {
  				self.__stepThroughDeceleration(render);
  			};
  
  			// How much velocity is required to keep the deceleration running
  			var minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.001;
  
  			// Detect whether it's still worth to continue animating steps
  			// If we are already slow enough to not being user perceivable anymore, we stop the whole process here.
  			var verify = function() {
  				var shouldContinue = Math.abs(self.__decelerationVelocityX) >= minVelocityToKeepDecelerating || Math.abs(self.__decelerationVelocityY) >= minVelocityToKeepDecelerating;
  				if (!shouldContinue) {
  					self.__didDecelerationComplete = true;
  				}
  				return shouldContinue;
  			};
  
  			var completed = function(renderedFramesPerSecond, animationId, wasFinished) {
  				self.__isDecelerating = false;
  				if (self.__didDecelerationComplete) {
  					self.options.scrollingComplete();
  				}
  
  				// Animate to grid when snapping is active, otherwise just fix out-of-boundary positions
  				self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);
  			};
  
  			// Start animation and switch on flag
  			self.__isDecelerating = core.effect.Animate.start(step, verify, completed);
  
  		},
  
  
  		/**
  		 * Called on every step of the animation
  		 *
  		 * @param inMemory {Boolean?false} Whether to not render the current step, but keep it in memory only. Used internally only!
  		 */
  		__stepThroughDeceleration: function(render) {
  
  			var self = this;
  
  
  			//
  			// COMPUTE NEXT SCROLL POSITION
  			//
  
  			// Add deceleration to scroll position
  			var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;
  			var scrollTop = self.__scrollTop + self.__decelerationVelocityY;
  
  
  			//
  			// HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE
  			//
  
  			if (!self.options.bouncing) {
  
  				var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);
  				if (scrollLeftFixed !== scrollLeft) {
  					scrollLeft = scrollLeftFixed;
  					self.__decelerationVelocityX = 0;
  				}
  
  				var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);
  				if (scrollTopFixed !== scrollTop) {
  					scrollTop = scrollTopFixed;
  					self.__decelerationVelocityY = 0;
  				}
  
  			}
  
  
  			//
  			// UPDATE SCROLL POSITION
  			//
  
  			if (render) {
  
  				self.__publish(scrollLeft, scrollTop, self.__zoomLevel);
  
  			} else {
  
  				self.__scrollLeft = scrollLeft;
  				self.__scrollTop = scrollTop;
  
  			}
  
  
  			//
  			// SLOW DOWN
  			//
  
  			// Slow down velocity on every iteration
  			if (!self.options.paging) {
  
  				// This is the factor applied to every iteration of the animation
  				// to slow down the process. This should emulate natural behavior where
  				// objects slow down when the initiator of the movement is removed
  				var frictionFactor = 0.95;
  
  				self.__decelerationVelocityX *= frictionFactor;
  				self.__decelerationVelocityY *= frictionFactor;
  
  			}
  
  
  			//
  			// BOUNCING SUPPORT
  			//
  
  			if (self.options.bouncing) {
  
  				var scrollOutsideX = 0;
  				var scrollOutsideY = 0;
  
  				// This configures the amount of change applied to deceleration/acceleration when reaching boundaries
  				var penetrationDeceleration = self.options.penetrationDeceleration;
  				var penetrationAcceleration = self.options.penetrationAcceleration;
  
  				// Check limits
  				if (scrollLeft < self.__minDecelerationScrollLeft) {
  					scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;
  				} else if (scrollLeft > self.__maxDecelerationScrollLeft) {
  					scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;
  				}
  
  				if (scrollTop < self.__minDecelerationScrollTop) {
  					scrollOutsideY = self.__minDecelerationScrollTop - scrollTop;
  				} else if (scrollTop > self.__maxDecelerationScrollTop) {
  					scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;
  				}
  
  				// Slow down until slow enough, then flip back to snap position
  				if (scrollOutsideX !== 0) {
  					if (scrollOutsideX * self.__decelerationVelocityX <= 0) {
  						self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;
  					} else {
  						self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;
  					}
  				}
  
  				if (scrollOutsideY !== 0) {
  					if (scrollOutsideY * self.__decelerationVelocityY <= 0) {
  						self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;
  					} else {
  						self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;
  					}
  				}
  			}
  		}
  	};
  
  	// Copy over members to prototype
  	for (var key in members) {
  		Scroller.prototype[key] = members[key];
  	}
  
    if (typeof module != 'undefined' && module.exports) {
      module.exports = Scroller;
    } else if (typeof define == 'function' && define.amd) {
      define( function () { return Scroller; } );
    } else {
      window.Scroller = Scroller;
    }
  
  })(window);
  