text: (function($) {
    var touchStatus = function(target, touch) {
      this.target    = $(target);
      this.touch     = touch;
      this.startX    = this.currentX = touch.screenX;
      this.startY    = this.currentY = touch.screenY;
      this.eventType = null;
    }
    touchStatus.latestTap = null;
  
    touchStatus.prototype.move = function(touch) {
      this.currentX = touch.screenX;
      this.currentY = touch.screenY;
    }
  
    touchStatus.prototype.process = function() {
      var offsetX = this.currentX - this.startX;
      var offsetY = this.currentY - this.startY;
      if(offsetX == 0 && offsetY == 0) {
        this.checkForDoubleTap()
      } else if(Math.abs(offsetY) > Math.abs(offsetX)) {
        this.eventType = offsetY > 0 ? 'swipedown' : 'swipeup';
        this.target.trigger('swipe', [this])
      } else {
        this.eventType = offsetX > 0 ? 'swiperight' : 'swipeleft';
        this.target.trigger('swipe', [this])
      }
      this.target.trigger(this.eventType, [this])
      this.target.trigger('touch',        [this])
    }
  
    touchStatus.prototype.checkForDoubleTap = function() {
      if(touchStatus.latestTap) {
        if((new Date() - touchStatus.latestTap) < 400) 
          this.eventType = 'doubletap'
      }
      if(!this.eventType) this.eventType = 'tap'
      touchStatus.latestTap = new Date()
    }
  
    var swipeEvents = function(elements) {
      elements.bind('touchstart',  this.touchStart);
      elements.bind('touchmove',   this.touchMove);
      elements.bind('touchcancel', this.touchCancel);
      elements.bind('touchend',    this.touchEnd);
    }
  
    swipeEvents.prototype.touchStart = function(evt) {
      var target = this;
      swipeEvents.eachTouch(evt, function(touch) {
        swipeEvents.touches[touch.identifier] = new touchStatus(target, touch);
      })
    }
  
    swipeEvents.prototype.touchMove = function(evt) {
      swipeEvents.eachTouch(evt, function(touch) {
        var loc = swipeEvents.touches[touch.identifier]
        if(loc) loc.move(touch)
      })
    }
  
    swipeEvents.prototype.touchCancel = function(evt) {
      swipeEvents.eachTouch(evt, function(touch) {
        swipeEvents.purge(touch, true)
      })
    }
  
    swipeEvents.prototype.touchEnd = function(evt) {
      swipeEvents.eachTouch(evt, function(touch) {
        swipeEvents.purge(touch)
      })
    }
  
    swipeEvents.touches = {}
    swipeEvents.purge = function(touch, cancelled) {
      if(!cancelled) {
        var loc = swipeEvents.touches[touch.identifier]
        if(loc) loc.process()
      }
      delete swipeEvents.touches[touch.identifier]
    }
  
    swipeEvents.eachTouch = function(evt, callback) {
      var evt = evt.originalEvent;
      var num = evt.changedTouches.length;
      for(var i = 0; i < num; i++) {
        callback(evt.changedTouches[i])
      }
    }
  
    // adds custom events:
    //   touch      // all events
    //   swipe      // only swipe* events
    //   swipeleft
    //   swiperight
    //   swipeup
    //   swipedown
    //   tap
    //   doubletap
    $.fn.addSwipeEvents = function(callback) {
      new swipeEvents(this);
      if(callback) this.bind('touch', callback)
      return this;
    }
  })(jQuery);