text: var Scroll = {
    positions: {},
    history: [],
    historyIndex: 0,
  };
  
  const NON_SCROLLABLE     = 0,
        SCROLLABLE_Y_DOWN  = 1,
        SCROLLABLE_Y_UP    = 2,
        SCROLLABLE_X_RIGHT = 4,
        SCROLLABLE_X_LEFT  = 8,
        SCROLLABLE = SCROLLABLE_X_LEFT | SCROLLABLE_X_RIGHT |
                     SCROLLABLE_Y_UP | SCROLLABLE_Y_DOWN;
  // const SCROLLABLE_Y = SCROLLABLE_Y_UP   | SCROLLABLE_Y_DOWN,
  //       SCROLLABLE_X = SCROLLABLE_X_LEFT | SCROLLABLE_X_RIGHT;
  
  var scrollingElement = (function() {
    function getScrollType(elem) {
      var cs = getComputedStyle(elem);
      var st = NON_SCROLLABLE;
      if (cs.overflow === 'hidden')
        return st;
      if (cs.overflowX !== 'hidden' &&
          elem.offsetHeight > elem.clientHeight &&
          elem.scrollWidth > elem.clientWidth) {
        if (elem.scrollLeft > 0)
          st |= SCROLLABLE_X_LEFT;
        if (elem.scrollLeft + elem.clientWidth < elem.scrollWidth)
          st |= SCROLLABLE_X_RIGHT;
      }
      if (cs.overflowY !== 'hidden' &&
          elem.offsetWidth > elem.clientWidth &&
          elem.scrollHeight > elem.clientHeight) {
        if (elem.scrollTop > 0)
          st |= SCROLLABLE_Y_UP;
        if (elem.scrollTop + elem.clientHeight < elem.scrollHeight)
          st |= SCROLLABLE_Y_DOWN;
      }
      return st;
    }
  
    var lastActiveElem = null,
        lastScrollElem = null,
        clickFocus = false;
  
    window.resetScrollFocus = function() {
      clickFocus = false;
    };
  
    document.addEventListener('mousedown', function(event) {
      if (!event.isTrusted)
        return true;
      clickFocus = true;
      lastActiveElem = event.srcElement;
    });
  
    return function scrollingElement(dir) {
      var elem;
      if (clickFocus) {
        elem = lastActiveElem;
      } else {
        elem = lastActiveElem = document.activeElement;
      }
      if (elem === null)
        return null;
      return (function climb(elem) {
        if (elem === null)
          return lastScrollElem || document.scrollingElement;
        if (elem === document.scrollingElement)
          return elem;
        var st = getScrollType(elem);
        return st & dir ? elem : climb(elem.parentElement);
      })(elem);
    };
  })();
  
  function $scrollBy(elem, x, y) {
    elem.scrollLeft += x;
    elem.scrollTop += y;
  }
  function $scrollTo(elem, x, y) {
    if (x !== null)
      elem.scrollLeft = x;
    if (y !== null)
      elem.scrollTop = y;
  }
  
  (function($) {
  
    var animationYFrame, animationXFrame,
        scrollXFunction, scrollYFunction,
        scrollElem, scrollXElem,
        lastX, lastY;
    var holdKeyScroll = false;
  
    var easeFn = function(t, b, c, d) {
      return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
    };
  
    var timeFn = typeof window.performance === 'undefined' ?
      Date.now : performance.now.bind(performance);
  
    var scroll = {
      x0:   0, // starting x position
      x1:   0, // ending x position
      xc:   0, // delta-x during scroll
      tx:   0, // delta-t
      txo:  0, // last time measurement
      dx:   0, // x-duration
      y0:   0,
      y1:   0,
      yc:   0,
      ty:   0,
      tyo:  0,
      dy:   0,
      callback: null,
    };
    var scrollx = Object.clone(scroll);
  
    scrollYFunction = function() {
      var delta = easeFn(scroll.ty, scroll.y0, scroll.y1 - scroll.y0, scroll.dy);
      var time = timeFn();
      scroll.yc = delta;
      scroll.ty += time - scroll.tyo;
      scroll.tyo = time;
      $scrollTo(scrollElem, null, delta);
      if (!holdKeyScroll && scroll.ty <= scroll.dy) {
        animationYFrame = $.requestAnimationFrame(scrollYFunction);
      } else {
        $.cancelAnimationFrame(animationYFrame);
        animationYFrame = null;
        $scrollTo(scrollElem, null, scroll.y1);
        scroll.y0 = scroll.y1 = scroll.yc = scroll.ty = 0;
        if (scroll.callback)
          scroll.callback();
      }
    };
  
    scrollXFunction = function() {
      var delta = easeFn(scrollx.tx, scrollx.x0, scrollx.x1 - scrollx.x0, scrollx.dx);
      var time = timeFn();
      scrollx.xc = delta;
      scrollx.tx += time - scrollx.txo;
      scrollx.txo = time;
      $scrollTo(scrollXElem, delta, null);
      if (!holdKeyScroll && scrollx.tx <= scrollx.dx) {
        animationXFrame = $.requestAnimationFrame(scrollXFunction);
      } else {
        $.cancelAnimationFrame(animationXFrame);
        $scrollTo(scrollXElem, scrollx.x1, null);
        scrollx.x0 = scrollx.x1 = scrollx.xc = scrollx.tx = 0;
        if (scroll.callback)
          scroll.callback();
      }
    };
  
    $.setSmoothScrollEaseFN = function(fn) {
      easeFn = fn;
    };
  
    $.smoothScrollTo = function(elem, x, y, d, callback) {
      scrollElem = elem;
      $.cancelAnimationFrame(animationXFrame);
      $.cancelAnimationFrame(animationYFrame);
      scroll.dx = scroll.dy = d;
      scrollx.dx = scrollx.dy = d;
      scroll.callback = callback || function() {};
      if (x !== scrollElem.scrollLeft) {
        scrollXElem = elem;
        scrollx.x0 = scrollXElem.scrollLeft;
        scrollx.x1 = x;
        scrollx.tx = 0;
        scrollx.txo = timeFn();
        scrollXFunction();
      }
      if (y !== scrollElem.scrollTop) {
        scroll.y0 = scrollElem.scrollTop;
        scroll.y1 = y;
        scroll.ty = 0;
        scroll.tyo = timeFn();
        scrollYFunction();
      }
    };
  
    var holdFunction = function(dx, dy) {
      var se = dx ? scrollXElem : scrollElem;
      return (function animationLoop() {
        if ($.scrollKeyUp) {
          holdKeyScroll = false;
          return;
        }
        $scrollBy(se, dx, dy);
        $.requestAnimationFrame(animationLoop);
      })();
    };
  
    $.smoothScrollBy = function(elem, x, y, d, callback) {
      if (x) {
        scrollXElem = elem;
      } else {
        scrollElem = elem;
      }
      if (!$.scrollKeyUp && x === lastX && y === lastY) {
        if (!holdKeyScroll) {
          holdKeyScroll = true;
          holdFunction(x * 0.25, y * 0.25);
        }
        return;
      }
      scroll.callback = callback || function() {};
      lastX = x;
      lastY = y;
      $.scrollKeyUp = false;
      holdKeyScroll = false;
      if (x) {
        var oldDx = scrollx.x1 - scrollx.xc;
        $.cancelAnimationFrame(animationXFrame);
        scrollx.dx = d;
        scrollx.x0 = scrollXElem.scrollLeft;
        scrollx.x1 = oldDx + scrollx.x0 + x;
        scrollx.tx = 0;
        scrollx.txo = timeFn();
        scrollXFunction();
      }
      if (y) {
        var oldDy = scroll.y1 - scroll.yc;
        scroll.dy = d;
        scroll.y0 = scrollElem.scrollTop;
        scroll.y1 = oldDy + scroll.y0 + y;
        scroll.ty = 0;
        scroll.tyo = timeFn();
        $.cancelAnimationFrame(animationYFrame);
        scrollYFunction();
      }
    };
  
    $.scrollKeyUp = true;
  
  })(this);
  
  Scroll.historyStateEquals = function(s1, s2) {
    if (!s1 || !s2)
      return false;
    return s1[0] === s2[0] &&
      s1[1] === s2[1] &&
      s1[2] === s2[2];
  };
  
  Scroll.scrollToHistoryState = function(index) {
    index = index || this.historyIndex;
    var state = this.history[index];
    if (!state)
      return;
    var scrollElem = state[0];
    scrollElem.scrollLeft = state[1];
    scrollElem.scrollTop = state[2];
    this.historyIndex = index;
  };
  
  Scroll.previousHistoryState = function() {
    if (!this.historyStateEquals(this.lastState(), this.currentState()))
      this.addHistoryState();
    if (this.historyIndex > 0) {
      this.historyIndex--;
      this.scrollToHistoryState(this.historyIndex);
    }
  };
  
  Scroll.nextHistoryState = function() {
    if (this.historyIndex + 1 < this.history.length) {
      this.historyIndex++;
      this.scrollToHistoryState(this.historyIndex);
    }
  };
  
  Scroll.currentState = function() {
    // TODO make work with nested scrolling elements
    // var scrollElem = scrollingElement(SCROLLABLE);
  
    var scrollElem = document.scrollingElement;
    if (!scrollElem)
      return null;
    return [scrollElem, scrollElem.scrollLeft, scrollElem.scrollTop];
  };
  
  Scroll.lastState = function() {
    if (this.historyIndex >= this.history.length)
      return null;
    return this.history[this.historyIndex];
  };
  
  Scroll.addHistoryState = function() {
    var nextState = this.currentState();
    if (!nextState)
      return false;
    if (this.historyIndex + 1 < this.history.length)
      this.history = this.history.slice(0, this.historyIndex + 1);
    if (this.history.length) {
      if (this.historyStateEquals(this.lastState(), nextState))
        return false;
    }
    this.history.push(nextState);
    this.historyIndex = this.history.length - 1;
    return true;
  };
  
  Scroll.scroll = function(type, repeats) {
  
    var stepSize = settings ? settings.scrollstep : 60;
  
    var shouldLogPosition = !/^(up|down|left|right|pageUp|pageDown)$/.test(type);
    if (document.body && shouldLogPosition) {
      this.lastPosition = [document.scrollingElement.scrollLeft, document.scrollingElement.scrollTop];
      Scroll.addHistoryState();
    }
  
    var direction = (function() {
      switch (type) {
      case 'up': case 'pageUp': case 'fullPageUp': case 'top':
        return SCROLLABLE_Y_UP;
      case 'left': case 'leftmost':
        return SCROLLABLE_X_LEFT;
      case 'right': case 'rightmost':
        return SCROLLABLE_X_RIGHT;
      default:
        return SCROLLABLE_Y_DOWN;
      }
    })();
  
    var scrollElem = scrollingElement(direction),
        hy = scrollElem === document.body ? innerHeight : scrollElem.clientHeight,
        hw = scrollElem === document.body ? innerWidth  : scrollElem.clientWidth,
        x = 0,
        y = 0;
  
    switch (type) {
    case 'down':
      y = repeats * stepSize;
      break;
    case 'up':
      y -= repeats * stepSize;
      break;
    case 'pageDown':
      y = repeats * hy >> 1;
      break;
    case 'fullPageDown':
      y = repeats * hy * (settings.fullpagescrollpercent / 100 || 1);
      break;
    case 'pageUp':
      y -= repeats * hy >> 1;
      break;
    case 'fullPageUp':
      y -= repeats * hy * (settings.fullpagescrollpercent / 100 || 1);
      break;
    case 'top':
      y -= scrollElem.scrollTop;
      break;
    case 'bottom':
      y = scrollElem.scrollHeight - scrollElem.scrollTop - hy + 20;
      break;
    case 'left':
      x -= repeats * stepSize >> 1;
      break;
    case 'right':
      x = repeats * stepSize >> 1;
      break;
    case 'leftmost':
      x -= scrollElem.scrollLeft;
      break;
    case 'rightmost':
      x = scrollElem.scrollWidth - scrollElem.scrollLeft - hw + 20;
      break;
    }
  
    if (settings && settings.smoothscroll) {
      window.smoothScrollBy(scrollElem, x, y, settings.scrollduration);
    } else {
      $scrollBy(scrollElem, x, y);
    }
  };
  