text: // FINGERBLAST.js
  // --------------
  // Adapted from phantom limb by Brian Cartensen
  
  /* jshint bitwise: false */
  /* global GLOBAL: true */
  
  (function () {
  
    'use strict';
  
    function FingerBlast (element) {
      this.element = typeof element === 'string' ? document.querySelector(element) : element;
  
      if (this.element) {
        this.listen();
      }
    }
  
    FingerBlast.prototype = {
      x: NaN,
      y: NaN,
  
      startDistance: NaN,
      startAngle:    NaN,
  
      mouseIsDown: false,
  
      listen: function () {
        var activate = this.activate.bind(this);
        var deactivate = this.deactivate.bind(this);
  
        function contains (element, ancestor) {
          var descendants;
          var index;
          var descendant;
  
          if (!element) {
            return;
          }
  
          if ('compareDocumentPosition' in ancestor) {
            return !!(ancestor.compareDocumentPosition(element) & 16);
          } else if ('contains' in ancestor) {
            return ancestor !== element && ancestor.contains(element);
          } else {
            for ((descendants = ancestor.getElementsByTagName('*')), index = 0; (descendant = descendants[index++]);) {
              if (descendant === element) {
                return true;
              }
            }
            return false;
          }
        }
  
        this.element.addEventListener('mouseover', function (e) {
          var target = e.relatedTarget;
          if (target !== this && !contains(target, this)) {
            activate();
          }
        });
  
        this.element.addEventListener('mouseout', function (e) {
          var target = e.relatedTarget;
          if (target !== this && !contains(target, this)) {
            deactivate(e);
          }
        });
      },
  
      activate: function () {
        if (this.active) {
          return;
        }
        this.element.addEventListener('mousedown', (this.touchStart = this.touchStart.bind(this)), true);
        this.element.addEventListener('mousemove', (this.touchMove  = this.touchMove.bind(this)),  true);
        this.element.addEventListener('mouseup',   (this.touchEnd   = this.touchEnd.bind(this)),   true);
        this.element.addEventListener('click',     (this.click      = this.click.bind(this)),      true);
        this.active = true;
      },
  
      deactivate: function (e) {
        this.active = false;
        if (this.mouseIsDown) {
          this.touchEnd(e);
        }
        this.element.removeEventListener('mousedown', this.touchStart, true);
        this.element.removeEventListener('mousemove', this.touchMove,  true);
        this.element.removeEventListener('mouseup',   this.touchEnd,   true);
        this.element.removeEventListener('click',     this.click,      true);
      },
  
      click: function (e) {
        if (e.synthetic) {
          return;
        }
        e.preventDefault();
        e.stopPropagation();
      },
  
      touchStart: function (e) {
        if (e.synthetic || /input|textarea/.test(e.target.tagName.toLowerCase())) {
          return;
        }
  
        this.mouseIsDown = true;
  
        e.preventDefault();
        e.stopPropagation();
  
        this.fireTouchEvents('touchstart', e);
      },
  
      touchMove: function (e) {
        if (e.synthetic) {
          return;
        }
  
        e.preventDefault();
        e.stopPropagation();
  
        this.move(e.clientX, e.clientY);
  
        if (this.mouseIsDown) {
          this.fireTouchEvents('touchmove', e);
        }
      },
  
      touchEnd: function (e) {
        if (e.synthetic) {
          return;
        }
  
        this.mouseIsDown = false;
  
        e.preventDefault();
        e.stopPropagation();
  
        this.fireTouchEvents('touchend', e);
  
        if (!this.target) {
          return;
        }
  
        // Mobile Safari moves all the mouse events to fire after the touchend event.
        this.target.dispatchEvent(this.createMouseEvent('mouseover', e));
        this.target.dispatchEvent(this.createMouseEvent('mousemove', e));
        this.target.dispatchEvent(this.createMouseEvent('mousedown', e));
      },
  
      fireTouchEvents: function (eventName, originalEvent) {
        var events   = [];
        var gestures = [];
  
        if (!this.target) {
          return;
        }
  
        // Convert 'ontouch*' properties and attributes to listeners.
        var onEventName = 'on' + eventName;
  
        if (onEventName in this.target) {
          console.warn('Converting `' + onEventName + '` property to event listener.', this.target);
          this.target.addEventListener(eventName, this.target[onEventName], false);
          delete this.target[onEventName];
        }
  
        if (this.target.hasAttribute(onEventName)) {
          console.warn('Converting `' + onEventName + '` attribute to event listener.', this.target);
          var handler = new GLOBAL.Function('event', this.target.getAttribute(onEventName));
          this.target.addEventListener(eventName, handler, false);
          this.target.removeAttribute(onEventName);
        }
  
        // Set up a new event with the coordinates of the finger.
        var touch = this.createMouseEvent(eventName, originalEvent);
  
        events.push(touch);
  
        // Figure out scale and rotation.
        if (events.length > 1) {
          var x = events[0].pageX - events[1].pageX;
          var y = events[0].pageY - events[1].pageY;
  
          var distance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
          var angle = Math.atan2(x, y) * (180 / Math.PI);
  
          var gestureName = 'gesturechange';
  
          if (eventName === 'touchstart') {
            gestureName = 'gesturestart';
            this.startDistance = distance;
            this.startAngle = angle;
          }
  
          if (eventName === 'touchend') {
            gestureName = 'gestureend';
          }
  
          events.forEach(function (event) {
            var gesture = this.createMouseEvent.call(event._finger, gestureName, event);
            gestures.push(gesture);
          }.bind(this));
  
          events.concat(gestures).forEach(function (event) {
            event.scale = distance / this.startDistance;
            event.rotation = this.startAngle - angle;
          });
        }
  
        // Loop through the events array and fill in each touch array.
        events.forEach(function (touch) {
          touch.touches = events.filter(function (e) {
            return ~e.type.indexOf('touch') && e.type !== 'touchend';
          });
  
          touch.changedTouches = events.filter(function (e) {
            return ~e.type.indexOf('touch') && e._finger.target === touch._finger.target;
          });
  
          touch.targetTouches = touch.changedTouches.filter(function (e) {
            return ~e.type.indexOf('touch') && e.type !== 'touchend';
          });
        });
  
        // Then fire the events.
        events.concat(gestures).forEach(function (event, i) {
          event.identifier = i;
          event._finger.target.dispatchEvent(event);
        });
      },
  
      createMouseEvent: function (eventName, originalEvent) {
        var e = new MouseEvent(eventName, {
          view       : window,
          detail     : originalEvent.detail,
          bubbles    : true,
          cancelable : true,
          target     : this.target || originalEvent.relatedTarget,
          clientX    : this.x || originalEvent.clientX,
          clientY    : this.y || originalEvent.clientY,
          screenX    : this.x || originalEvent.screenX,
          screenY    : this.y || originalEvent.screenY,
          ctrlKey    : originalEvent.ctrlKey,
          shiftKey   : originalEvent.shiftKey,
          altKey     : originalEvent.altKey,
          metaKey    : originalEvent.metaKey,
          button     : originalEvent.button
        });
  
        e.synthetic = true;
        e._finger   = this;
  
        return e;
      },
  
      move: function (x, y) {
        if (isNaN(x) || isNaN(y)) {
          this.target = null;
        } else {
          this.x = x;
          this.y = y;
  
          if (!this.mouseIsDown) {
            this.target = document.elementFromPoint(x, y);
          }
        }
      }
    };
  
    window.FingerBlast = FingerBlast;
  
  }());