text: /**
   * tooltipster http://iamceege.github.io/tooltipster/
   * A rockin' custom tooltip jQuery plugin
   * Developed by Caleb Jacob and Louis Ameline
   * MIT license
   */
  (function (root, factory) {
    if (typeof define === 'function' && define.amd) {
      // AMD. Register as an anonymous module unless amdModuleId is set
      define(["jquery"], function (a0) {
        return (factory(a0));
      });
    } else if (typeof exports === 'object') {
      // Node. Does not work with strict CommonJS, but
      // only CommonJS-like environments that support module.exports,
      // like Node.
      module.exports = factory(require("jquery"));
    } else {
      factory(jQuery);
    }
  }(this, function ($) {
  
  // This file will be UMDified by a build task.
  
  var defaults = {
  		animation: 'fade',
  		animationDuration: 350,
  		content: null,
  		contentAsHTML: false,
  		contentCloning: false,
  		debug: true,
  		delay: 300,
  		delayTouch: [300, 500],
  		functionInit: null,
  		functionBefore: null,
  		functionReady: null,
  		functionAfter: null,
  		functionFormat: null,
  		IEmin: 6,
  		interactive: false,
  		multiple: false,
  		// will default to document.body, or must be an element positioned at (0, 0)
  		// in the document, typically like the very top views of an app.
  		parent: null,
  		plugins: ['sideTip'],
  		repositionOnScroll: false,
  		restoration: 'none',
  		selfDestruction: true,
  		theme: [],
  		timer: 0,
  		trackerInterval: 500,
  		trackOrigin: false,
  		trackTooltip: false,
  		trigger: 'hover',
  		triggerClose: {
  			click: false,
  			mouseleave: false,
  			originClick: false,
  			scroll: false,
  			tap: false,
  			touchleave: false
  		},
  		triggerOpen: {
  			click: false,
  			mouseenter: false,
  			tap: false,
  			touchstart: false
  		},
  		updateAnimation: 'rotate',
  		zIndex: 9999999
  	},
  	// we'll avoid using the 'window' global as a good practice but npm's
  	// jquery@<2.1.0 package actually requires a 'window' global, so not sure
  	// it's useful at all
  	win = (typeof window != 'undefined') ? window : null,
  	// env will be proxied by the core for plugins to have access its properties
  	env = {
  		// detect if this device can trigger touch events. Better have a false
  		// positive (unused listeners, that's ok) than a false negative.
  		// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
  		// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
  		hasTouchCapability: !!(
  			win
  			&&	(	'ontouchstart' in win
  				||	(win.DocumentTouch && win.document instanceof win.DocumentTouch)
  				||	win.navigator.maxTouchPoints
  			)
  		),
  		hasTransitions: transitionSupport(),
  		IE: false,
  		// don't set manually, it will be updated by a build task after the manifest
  		semVer: '4.2.3',
  		window: win
  	},
  	core = function() {
  		
  		// core variables
  		
  		// the core emitters
  		this.__$emitterPrivate = $({});
  		this.__$emitterPublic = $({});
  		this.__instancesLatestArr = [];
  		// collects plugin constructors
  		this.__plugins = {};
  		// proxy env variables for plugins who might use them
  		this._env = env;
  	};
  
  // core methods
  core.prototype = {
  	
  	/**
  	 * A function to proxy the public methods of an object onto another
  	 *
  	 * @param {object} constructor The constructor to bridge
  	 * @param {object} obj The object that will get new methods (an instance or the core)
  	 * @param {string} pluginName A plugin name for the console log message
  	 * @return {core}
  	 * @private
  	 */
  	__bridge: function(constructor, obj, pluginName) {
  		
  		// if it's not already bridged
  		if (!obj[pluginName]) {
  			
  			var fn = function() {};
  			fn.prototype = constructor;
  			
  			var pluginInstance = new fn();
  			
  			// the _init method has to exist in instance constructors but might be missing
  			// in core constructors
  			if (pluginInstance.__init) {
  				pluginInstance.__init(obj);
  			}
  			
  			$.each(constructor, function(methodName, fn) {
  				
  				// don't proxy "private" methods, only "protected" and public ones
  				if (methodName.indexOf('__') != 0) {
  					
  					// if the method does not exist yet
  					if (!obj[methodName]) {
  						
  						obj[methodName] = function() {
  							return pluginInstance[methodName].apply(pluginInstance, Array.prototype.slice.apply(arguments));
  						};
  						
  						// remember to which plugin this method corresponds (several plugins may
  						// have methods of the same name, we need to be sure)
  						obj[methodName].bridged = pluginInstance;
  					}
  					else if (defaults.debug) {
  						
  						console.log('The '+ methodName +' method of the '+ pluginName
  							+' plugin conflicts with another plugin or native methods');
  					}
  				}
  			});
  			
  			obj[pluginName] = pluginInstance;
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * For mockup in Node env if need be, for testing purposes
  	 *
  	 * @return {core}
  	 * @private
  	 */
  	__setWindow: function(window) {
  		env.window = window;
  		return this;
  	},
  	
  	/**
  	 * Returns a ruler, a tool to help measure the size of a tooltip under
  	 * various settings. Meant for plugins
  	 * 
  	 * @see Ruler
  	 * @return {object} A Ruler instance
  	 * @protected
  	 */
  	_getRuler: function($tooltip) {
  		return new Ruler($tooltip);
  	},
  	
  	/**
  	 * For internal use by plugins, if needed
  	 *
  	 * @return {core}
  	 * @protected
  	 */
  	_off: function() {
  		this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * For internal use by plugins, if needed
  	 *
  	 * @return {core}
  	 * @protected
  	 */
  	_on: function() {
  		this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * For internal use by plugins, if needed
  	 *
  	 * @return {core}
  	 * @protected
  	 */
  	_one: function() {
  		this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * Returns (getter) or adds (setter) a plugin
  	 *
  	 * @param {string|object} plugin Provide a string (in the full form
  	 * "namespace.name") to use as as getter, an object to use as a setter
  	 * @return {object|core}
  	 * @protected
  	 */
  	_plugin: function(plugin) {
  		
  		var self = this;
  		
  		// getter
  		if (typeof plugin == 'string') {
  			
  			var pluginName = plugin,
  				p = null;
  			
  			// if the namespace is provided, it's easy to search
  			if (pluginName.indexOf('.') > 0) {
  				p = self.__plugins[pluginName];
  			}
  			// otherwise, return the first name that matches
  			else {
  				$.each(self.__plugins, function(i, plugin) {
  					
  					if (plugin.name.substring(plugin.name.length - pluginName.length - 1) == '.'+ pluginName) {
  						p = plugin;
  						return false;
  					}
  				});
  			}
  			
  			return p;
  		}
  		// setter
  		else {
  			
  			// force namespaces
  			if (plugin.name.indexOf('.') < 0) {
  				throw new Error('Plugins must be namespaced');
  			}
  			
  			self.__plugins[plugin.name] = plugin;
  			
  			// if the plugin has core features
  			if (plugin.core) {
  				
  				// bridge non-private methods onto the core to allow new core methods
  				self.__bridge(plugin.core, self, plugin.name);
  			}
  			
  			return this;
  		}
  	},
  	
  	/**
  	 * Trigger events on the core emitters
  	 * 
  	 * @returns {core}
  	 * @protected
  	 */
  	_trigger: function() {
  		
  		var args = Array.prototype.slice.apply(arguments);
  		
  		if (typeof args[0] == 'string') {
  			args[0] = { type: args[0] };
  		}
  		
  		// note: the order of emitters matters
  		this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
  		this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
  		
  		return this;
  	},
  	
  	/**
  	 * Returns instances of all tooltips in the page or an a given element
  	 *
  	 * @param {string|HTML object collection} selector optional Use this
  	 * parameter to restrict the set of objects that will be inspected
  	 * for the retrieval of instances. By default, all instances in the
  	 * page are returned.
  	 * @return {array} An array of instance objects
  	 * @public
  	 */
  	instances: function(selector) {
  		
  		var instances = [],
  			sel = selector || '.tooltipstered';
  		
  		$(sel).each(function() {
  			
  			var $this = $(this),
  				ns = $this.data('tooltipster-ns');
  			
  			if (ns) {
  				
  				$.each(ns, function(i, namespace) {
  					instances.push($this.data(namespace));
  				});
  			}
  		});
  		
  		return instances;
  	},
  	
  	/**
  	 * Returns the Tooltipster objects generated by the last initializing call
  	 *
  	 * @return {array} An array of instance objects
  	 * @public
  	 */
  	instancesLatest: function() {
  		return this.__instancesLatestArr;
  	},
  	
  	/**
  	 * For public use only, not to be used by plugins (use ::_off() instead)
  	 *
  	 * @return {core}
  	 * @public
  	 */
  	off: function() {
  		this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * For public use only, not to be used by plugins (use ::_on() instead)
  	 *
  	 * @return {core}
  	 * @public
  	 */
  	on: function() {
  		this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * For public use only, not to be used by plugins (use ::_one() instead)
  	 * 
  	 * @return {core}
  	 * @public
  	 */
  	one: function() {
  		this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * Returns all HTML elements which have one or more tooltips
  	 *
  	 * @param {string} selector optional Use this to restrict the results
  	 * to the descendants of an element
  	 * @return {array} An array of HTML elements
  	 * @public
  	 */
  	origins: function(selector) {
  		
  		var sel = selector ?
  			selector +' ' :
  			'';
  		
  		return $(sel +'.tooltipstered').toArray();
  	},
  	
  	/**
  	 * Change default options for all future instances
  	 *
  	 * @param {object} d The options that should be made defaults
  	 * @return {core}
  	 * @public
  	 */
  	setDefaults: function(d) {
  		$.extend(defaults, d);
  		return this;
  	},
  	
  	/**
  	 * For users to trigger their handlers on the public emitter
  	 * 
  	 * @returns {core}
  	 * @public
  	 */
  	triggerHandler: function() {
  		this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		return this;
  	}
  };
  
  // $.tooltipster will be used to call core methods
  $.tooltipster = new core();
  
  // the Tooltipster instance class (mind the capital T)
  $.Tooltipster = function(element, options) {
  	
  	// list of instance variables
  	
  	// stack of custom callbacks provided as parameters to API methods
  	this.__callbacks = {
  		close: [],
  		open: []
  	};
  	// the schedule time of DOM removal
  	this.__closingTime;
  	// this will be the user content shown in the tooltip. A capital "C" is used
  	// because there is also a method called content()
  	this.__Content;
  	// for the size tracker
  	this.__contentBcr;
  	// to disable the tooltip after destruction
  	this.__destroyed = false;
  	// we can't emit directly on the instance because if a method with the same
  	// name as the event exists, it will be called by jQuery. Se we use a plain
  	// object as emitter. This emitter is for internal use by plugins,
  	// if needed.
  	this.__$emitterPrivate = $({});
  	// this emitter is for the user to listen to events without risking to mess
  	// with our internal listeners
  	this.__$emitterPublic = $({});
  	this.__enabled = true;
  	// the reference to the gc interval
  	this.__garbageCollector;
  	// various position and size data recomputed before each repositioning
  	this.__Geometry;
  	// the tooltip position, saved after each repositioning by a plugin
  	this.__lastPosition;
  	// a unique namespace per instance
  	this.__namespace = 'tooltipster-'+ Math.round(Math.random()*1000000);
  	this.__options;
  	// will be used to support origins in scrollable areas
  	this.__$originParents;
  	this.__pointerIsOverOrigin = false;
  	// to remove themes if needed
  	this.__previousThemes = [];
  	// the state can be either: appearing, stable, disappearing, closed
  	this.__state = 'closed';
  	// timeout references
  	this.__timeouts = {
  		close: [],
  		open: null
  	};
  	// store touch events to be able to detect emulated mouse events
  	this.__touchEvents = [];
  	// the reference to the tracker interval
  	this.__tracker = null;
  	// the element to which this tooltip is associated
  	this._$origin;
  	// this will be the tooltip element (jQuery wrapped HTML element).
  	// It's the job of a plugin to create it and append it to the DOM
  	this._$tooltip;
  	
  	// launch
  	this.__init(element, options);
  };
  
  $.Tooltipster.prototype = {
  	
  	/**
  	 * @param origin
  	 * @param options
  	 * @private
  	 */
  	__init: function(origin, options) {
  		
  		var self = this;
  		
  		self._$origin = $(origin);
  		self.__options = $.extend(true, {}, defaults, options);
  		
  		// some options may need to be reformatted
  		self.__optionsFormat();
  		
  		// don't run on old IE if asked no to
  		if (	!env.IE
  			||	env.IE >= self.__options.IEmin
  		) {
  			
  			// note: the content is null (empty) by default and can stay that
  			// way if the plugin remains initialized but not fed any content. The
  			// tooltip will just not appear.
  			
  			// let's save the initial value of the title attribute for later
  			// restoration if need be.
  			var initialTitle = null;
  			
  			// it will already have been saved in case of multiple tooltips
  			if (self._$origin.data('tooltipster-initialTitle') === undefined) {
  				
  				initialTitle = self._$origin.attr('title');
  				
  				// we do not want initialTitle to be "undefined" because
  				// of how jQuery's .data() method works
  				if (initialTitle === undefined) initialTitle = null;
  				
  				self._$origin.data('tooltipster-initialTitle', initialTitle);
  			}
  			
  			// If content is provided in the options, it has precedence over the
  			// title attribute.
  			// Note: an empty string is considered content, only 'null' represents
  			// the absence of content.
  			// Also, an existing title="" attribute will result in an empty string
  			// content
  			if (self.__options.content !== null) {
  				self.__contentSet(self.__options.content);
  			}
  			else {
  				
  				var selector = self._$origin.attr('data-tooltip-content'),
  					$el;
  				
  				if (selector){
  					$el = $(selector);
  				}
  				
  				if ($el && $el[0]) {
  					self.__contentSet($el.first());
  				}
  				else {
  					self.__contentSet(initialTitle);
  				}
  			}
  			
  			self._$origin
  				// strip the title off of the element to prevent the default tooltips
  				// from popping up
  				.removeAttr('title')
  				// to be able to find all instances on the page later (upon window
  				// events in particular)
  				.addClass('tooltipstered');
  			
  			// set listeners on the origin
  			self.__prepareOrigin();
  			
  			// set the garbage collector
  			self.__prepareGC();
  			
  			// init plugins
  			$.each(self.__options.plugins, function(i, pluginName) {
  				self._plug(pluginName);
  			});
  			
  			// to detect swiping
  			if (env.hasTouchCapability) {
  				$(env.window.document.body).on('touchmove.'+ self.__namespace +'-triggerOpen', function(event) {
  					self._touchRecordEvent(event);
  				});
  			}
  			
  			self
  				// prepare the tooltip when it gets created. This event must
  				// be fired by a plugin
  				._on('created', function() {
  					self.__prepareTooltip();
  				})
  				// save position information when it's sent by a plugin
  				._on('repositioned', function(e) {
  					self.__lastPosition = e.position;
  				});
  		}
  		else {
  			self.__options.disabled = true;
  		}
  	},
  	
  	/**
  	 * Insert the content into the appropriate HTML element of the tooltip
  	 * 
  	 * @returns {self}
  	 * @private
  	 */
  	__contentInsert: function() {
  		
  		var self = this,
  			$el = self._$tooltip.find('.tooltipster-content'),
  			formattedContent = self.__Content,
  			format = function(content) {
  				formattedContent = content;
  			};
  		
  		self._trigger({
  			type: 'format',
  			content: self.__Content,
  			format: format
  		});
  		
  		if (self.__options.functionFormat) {
  			
  			formattedContent = self.__options.functionFormat.call(
  				self,
  				self,
  				{ origin: self._$origin[0] },
  				self.__Content
  			);
  		}
  		
  		if (typeof formattedContent === 'string' && !self.__options.contentAsHTML) {
  			$el.text(formattedContent);
  		}
  		else {
  			$el
  				.empty()
  				.append(formattedContent);
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * Save the content, cloning it beforehand if need be
  	 * 
  	 * @param content
  	 * @returns {self}
  	 * @private
  	 */
  	__contentSet: function(content) {
  		
  		// clone if asked. Cloning the object makes sure that each instance has its
  		// own version of the content (in case a same object were provided for several
  		// instances)
  		// reminder: typeof null === object
  		if (content instanceof $ && this.__options.contentCloning) {
  			content = content.clone(true);
  		}
  		
  		this.__Content = content;
  		
  		this._trigger({
  			type: 'updated',
  			content: content
  		});
  		
  		return this;
  	},
  	
  	/**
  	 * Error message about a method call made after destruction
  	 * 
  	 * @private
  	 */
  	__destroyError: function() {
  		throw new Error('This tooltip has been destroyed and cannot execute your method call.');
  	},
  	
  	/**
  	 * Gather all information about dimensions and available space,
  	 * called before every repositioning
  	 * 
  	 * @private
  	 * @returns {object}
  	 */
  	__geometry: function() {
  		
  		var	self = this,
  			$target = self._$origin,
  			originIsArea = self._$origin.is('area');
  		
  		// if this._$origin is a map area, the target we'll need
  		// the dimensions of is actually the image using the map,
  		// not the area itself
  		if (originIsArea) {
  			
  			var mapName = self._$origin.parent().attr('name');
  			
  			$target = $('img[usemap="#'+ mapName +'"]');
  		}
  		
  		var bcr = $target[0].getBoundingClientRect(),
  			$document = $(env.window.document),
  			$window = $(env.window),
  			$parent = $target,
  			// some useful properties of important elements
  			geo = {
  				// available space for the tooltip, see down below
  				available: {
  					document: null,
  					window: null
  				},
  				document: {
  					size: {
  						height: $document.height(),
  						width: $document.width()
  					}
  				},
  				window: {
  					scroll: {
  						// the second ones are for IE compatibility
  						left: env.window.scrollX || env.window.document.documentElement.scrollLeft,
  						top: env.window.scrollY || env.window.document.documentElement.scrollTop
  					},
  					size: {
  						height: $window.height(),
  						width: $window.width()
  					}
  				},
  				origin: {
  					// the origin has a fixed lineage if itself or one of its
  					// ancestors has a fixed position
  					fixedLineage: false,
  					// relative to the document
  					offset: {},
  					size: {
  						height: bcr.bottom - bcr.top,
  						width: bcr.right - bcr.left
  					},
  					usemapImage: originIsArea ? $target[0] : null,
  					// relative to the window
  					windowOffset: {
  						bottom: bcr.bottom,
  						left: bcr.left,
  						right: bcr.right,
  						top: bcr.top
  					}
  				}
  			},
  			geoFixed = false;
  		
  		// if the element is a map area, some properties may need
  		// to be recalculated
  		if (originIsArea) {
  			
  			var shape = self._$origin.attr('shape'),
  				coords = self._$origin.attr('coords');
  			
  			if (coords) {
  				
  				coords = coords.split(',');
  				
  				$.map(coords, function(val, i) {
  					coords[i] = parseInt(val);
  				});
  			}
  			
  			// if the image itself is the area, nothing more to do
  			if (shape != 'default') {
  				
  				switch(shape) {
  					
  					case 'circle':
  						
  						var circleCenterLeft = coords[0],
  							circleCenterTop = coords[1],
  							circleRadius = coords[2],
  							areaTopOffset = circleCenterTop - circleRadius,
  							areaLeftOffset = circleCenterLeft - circleRadius;
  						
  						geo.origin.size.height = circleRadius * 2;
  						geo.origin.size.width = geo.origin.size.height;
  						
  						geo.origin.windowOffset.left += areaLeftOffset;
  						geo.origin.windowOffset.top += areaTopOffset;
  						
  						break;
  					
  					case 'rect':
  						
  						var areaLeft = coords[0],
  							areaTop = coords[1],
  							areaRight = coords[2],
  							areaBottom = coords[3];
  						
  						geo.origin.size.height = areaBottom - areaTop;
  						geo.origin.size.width = areaRight - areaLeft;
  						
  						geo.origin.windowOffset.left += areaLeft;
  						geo.origin.windowOffset.top += areaTop;
  						
  						break;
  					
  					case 'poly':
  						
  						var areaSmallestX = 0,
  							areaSmallestY = 0,
  							areaGreatestX = 0,
  							areaGreatestY = 0,
  							arrayAlternate = 'even';
  						
  						for (var i = 0; i < coords.length; i++) {
  							
  							var areaNumber = coords[i];
  							
  							if (arrayAlternate == 'even') {
  								
  								if (areaNumber > areaGreatestX) {
  									
  									areaGreatestX = areaNumber;
  									
  									if (i === 0) {
  										areaSmallestX = areaGreatestX;
  									}
  								}
  								
  								if (areaNumber < areaSmallestX) {
  									areaSmallestX = areaNumber;
  								}
  								
  								arrayAlternate = 'odd';
  							}
  							else {
  								if (areaNumber > areaGreatestY) {
  									
  									areaGreatestY = areaNumber;
  									
  									if (i == 1) {
  										areaSmallestY = areaGreatestY;
  									}
  								}
  								
  								if (areaNumber < areaSmallestY) {
  									areaSmallestY = areaNumber;
  								}
  								
  								arrayAlternate = 'even';
  							}
  						}
  						
  						geo.origin.size.height = areaGreatestY - areaSmallestY;
  						geo.origin.size.width = areaGreatestX - areaSmallestX;
  						
  						geo.origin.windowOffset.left += areaSmallestX;
  						geo.origin.windowOffset.top += areaSmallestY;
  						
  						break;
  				}
  			}
  		}
  		
  		// user callback through an event
  		var edit = function(r) {
  			geo.origin.size.height = r.height,
  				geo.origin.windowOffset.left = r.left,
  				geo.origin.windowOffset.top = r.top,
  				geo.origin.size.width = r.width
  		};
  		
  		self._trigger({
  			type: 'geometry',
  			edit: edit,
  			geometry: {
  				height: geo.origin.size.height,
  				left: geo.origin.windowOffset.left,
  				top: geo.origin.windowOffset.top,
  				width: geo.origin.size.width
  			}
  		});
  		
  		// calculate the remaining properties with what we got
  		
  		geo.origin.windowOffset.right = geo.origin.windowOffset.left + geo.origin.size.width;
  		geo.origin.windowOffset.bottom = geo.origin.windowOffset.top + geo.origin.size.height;
  		
  		geo.origin.offset.left = geo.origin.windowOffset.left + geo.window.scroll.left;
  		geo.origin.offset.top = geo.origin.windowOffset.top + geo.window.scroll.top;
  		geo.origin.offset.bottom = geo.origin.offset.top + geo.origin.size.height;
  		geo.origin.offset.right = geo.origin.offset.left + geo.origin.size.width;
  		
  		// the space that is available to display the tooltip relatively to the document
  		geo.available.document = {
  			bottom: {
  				height: geo.document.size.height - geo.origin.offset.bottom,
  				width: geo.document.size.width
  			},
  			left: {
  				height: geo.document.size.height,
  				width: geo.origin.offset.left
  			},
  			right: {
  				height: geo.document.size.height,
  				width: geo.document.size.width - geo.origin.offset.right
  			},
  			top: {
  				height: geo.origin.offset.top,
  				width: geo.document.size.width
  			}
  		};
  		
  		// the space that is available to display the tooltip relatively to the viewport
  		// (the resulting values may be negative if the origin overflows the viewport)
  		geo.available.window = {
  			bottom: {
  				// the inner max is here to make sure the available height is no bigger
  				// than the viewport height (when the origin is off screen at the top).
  				// The outer max just makes sure that the height is not negative (when
  				// the origin overflows at the bottom).
  				height: Math.max(geo.window.size.height - Math.max(geo.origin.windowOffset.bottom, 0), 0),
  				width: geo.window.size.width
  			},
  			left: {
  				height: geo.window.size.height,
  				width: Math.max(geo.origin.windowOffset.left, 0)
  			},
  			right: {
  				height: geo.window.size.height,
  				width: Math.max(geo.window.size.width - Math.max(geo.origin.windowOffset.right, 0), 0)
  			},
  			top: {
  				height: Math.max(geo.origin.windowOffset.top, 0),
  				width: geo.window.size.width
  			}
  		};
  		
  		while ($parent[0].tagName.toLowerCase() != 'html') {
  			
  			if ($parent.css('position') == 'fixed') {
  				geo.origin.fixedLineage = true;
  				break;
  			}
  			
  			$parent = $parent.parent();
  		}
  		
  		return geo;
  	},
  	
  	/**
  	 * Some options may need to be formated before being used
  	 * 
  	 * @returns {self}
  	 * @private
  	 */
  	__optionsFormat: function() {
  		
  		if (typeof this.__options.animationDuration == 'number') {
  			this.__options.animationDuration = [this.__options.animationDuration, this.__options.animationDuration];
  		}
  		
  		if (typeof this.__options.delay == 'number') {
  			this.__options.delay = [this.__options.delay, this.__options.delay];
  		}
  		
  		if (typeof this.__options.delayTouch == 'number') {
  			this.__options.delayTouch = [this.__options.delayTouch, this.__options.delayTouch];
  		}
  		
  		if (typeof this.__options.theme == 'string') {
  			this.__options.theme = [this.__options.theme];
  		}
  		
  		// determine the future parent
  		if (this.__options.parent === null) {
  			this.__options.parent = $(env.window.document.body);
  		}
  		else if (typeof this.__options.parent == 'string') {
  			this.__options.parent = $(this.__options.parent);
  		}
  		
  		if (this.__options.trigger == 'hover') {
  			
  			this.__options.triggerOpen = {
  				mouseenter: true,
  				touchstart: true
  			};
  			
  			this.__options.triggerClose = {
  				mouseleave: true,
  				originClick: true,
  				touchleave: true
  			};
  		}
  		else if (this.__options.trigger == 'click') {
  			
  			this.__options.triggerOpen = {
  				click: true,
  				tap: true
  			};
  			
  			this.__options.triggerClose = {
  				click: true,
  				tap: true
  			};
  		}
  		
  		// for the plugins
  		this._trigger('options');
  		
  		return this;
  	},
  	
  	/**
  	 * Schedules or cancels the garbage collector task
  	 *
  	 * @returns {self}
  	 * @private
  	 */
  	__prepareGC: function() {
  		
  		var self = this;
  		
  		// in case the selfDestruction option has been changed by a method call
  		if (self.__options.selfDestruction) {
  			
  			// the GC task
  			self.__garbageCollector = setInterval(function() {
  				
  				var now = new Date().getTime();
  				
  				// forget the old events
  				self.__touchEvents = $.grep(self.__touchEvents, function(event, i) {
  					// 1 minute
  					return now - event.time > 60000;
  				});
  				
  				// auto-destruct if the origin is gone
  				if (!bodyContains(self._$origin)) {
  					
  					self.close(function(){
  						self.destroy();
  					});
  				}
  			}, 20000);
  		}
  		else {
  			clearInterval(self.__garbageCollector);
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * Sets listeners on the origin if the open triggers require them.
  	 * Unlike the listeners set at opening time, these ones
  	 * remain even when the tooltip is closed. It has been made a
  	 * separate method so it can be called when the triggers are
  	 * changed in the options. Closing is handled in _open()
  	 * because of the bindings that may be needed on the tooltip
  	 * itself
  	 *
  	 * @returns {self}
  	 * @private
  	 */
  	__prepareOrigin: function() {
  		
  		var self = this;
  		
  		// in case we're resetting the triggers
  		self._$origin.off('.'+ self.__namespace +'-triggerOpen');
  		
  		// if the device is touch capable, even if only mouse triggers
  		// are asked, we need to listen to touch events to know if the mouse
  		// events are actually emulated (so we can ignore them)
  		if (env.hasTouchCapability) {
  			
  			self._$origin.on(
  				'touchstart.'+ self.__namespace +'-triggerOpen ' +
  				'touchend.'+ self.__namespace +'-triggerOpen ' +
  				'touchcancel.'+ self.__namespace +'-triggerOpen',
  				function(event){
  					self._touchRecordEvent(event);
  				}
  			);
  		}
  		
  		// mouse click and touch tap work the same way
  		if (	self.__options.triggerOpen.click
  			||	(self.__options.triggerOpen.tap && env.hasTouchCapability)
  		) {
  			
  			var eventNames = '';
  			if (self.__options.triggerOpen.click) {
  				eventNames += 'click.'+ self.__namespace +'-triggerOpen ';
  			}
  			if (self.__options.triggerOpen.tap && env.hasTouchCapability) {
  				eventNames += 'touchend.'+ self.__namespace +'-triggerOpen';
  			}
  			
  			self._$origin.on(eventNames, function(event) {
  				if (self._touchIsMeaningfulEvent(event)) {
  					self._open(event);
  				}
  			});
  		}
  		
  		// mouseenter and touch start work the same way
  		if (	self.__options.triggerOpen.mouseenter
  			||	(self.__options.triggerOpen.touchstart && env.hasTouchCapability)
  		) {
  			
  			var eventNames = '';
  			if (self.__options.triggerOpen.mouseenter) {
  				eventNames += 'mouseenter.'+ self.__namespace +'-triggerOpen ';
  			}
  			if (self.__options.triggerOpen.touchstart && env.hasTouchCapability) {
  				eventNames += 'touchstart.'+ self.__namespace +'-triggerOpen';
  			}
  			
  			self._$origin.on(eventNames, function(event) {
  				if (	self._touchIsTouchEvent(event)
  					||	!self._touchIsEmulatedEvent(event)
  				) {
  					self.__pointerIsOverOrigin = true;
  					self._openShortly(event);
  				}
  			});
  		}
  		
  		// info for the mouseleave/touchleave close triggers when they use a delay
  		if (	self.__options.triggerClose.mouseleave
  			||	(self.__options.triggerClose.touchleave && env.hasTouchCapability)
  		) {
  			
  			var eventNames = '';
  			if (self.__options.triggerClose.mouseleave) {
  				eventNames += 'mouseleave.'+ self.__namespace +'-triggerOpen ';
  			}
  			if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
  				eventNames += 'touchend.'+ self.__namespace +'-triggerOpen touchcancel.'+ self.__namespace +'-triggerOpen';
  			}
  			
  			self._$origin.on(eventNames, function(event) {
  				
  				if (self._touchIsMeaningfulEvent(event)) {
  					self.__pointerIsOverOrigin = false;
  				}
  			});
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * Do the things that need to be done only once after the tooltip
  	 * HTML element it has been created. It has been made a separate
  	 * method so it can be called when options are changed. Remember
  	 * that the tooltip may actually exist in the DOM before it is
  	 * opened, and present after it has been closed: it's the display
  	 * plugin that takes care of handling it.
  	 * 
  	 * @returns {self}
  	 * @private
  	 */
  	__prepareTooltip: function() {
  		
  		var self = this,
  			p = self.__options.interactive ? 'auto' : '';
  		
  		// this will be useful to know quickly if the tooltip is in
  		// the DOM or not 
  		self._$tooltip
  			.attr('id', self.__namespace)
  			.css({
  				// pointer events
  				'pointer-events': p,
  				zIndex: self.__options.zIndex
  			});
  		
  		// themes
  		// remove the old ones and add the new ones
  		$.each(self.__previousThemes, function(i, theme) {
  			self._$tooltip.removeClass(theme);
  		});
  		$.each(self.__options.theme, function(i, theme) {
  			self._$tooltip.addClass(theme);
  		});
  		
  		self.__previousThemes = $.merge([], self.__options.theme);
  		
  		return self;
  	},
  	
  	/**
  	 * Handles the scroll on any of the parents of the origin (when the
  	 * tooltip is open)
  	 *
  	 * @param {object} event
  	 * @returns {self}
  	 * @private
  	 */
  	__scrollHandler: function(event) {
  		
  		var self = this;
  		
  		if (self.__options.triggerClose.scroll) {
  			self._close(event);
  		}
  		else {
  			
  			// if the origin or tooltip have been removed: do nothing, the tracker will
  			// take care of it later
  			if (bodyContains(self._$origin) && bodyContains(self._$tooltip)) {
  				
  				// if the scroll happened on the window
  				if (event.target === env.window.document) {
  					
  					// if the origin has a fixed lineage, window scroll will have no
  					// effect on its position nor on the position of the tooltip
  					if (!self.__Geometry.origin.fixedLineage) {
  						
  						// we don't need to do anything unless repositionOnScroll is true
  						// because the tooltip will already have moved with the window
  						// (and of course with the origin)
  						if (self.__options.repositionOnScroll) {
  							self.reposition(event);
  						}
  					}
  				}
  				// if the scroll happened on another parent of the tooltip, it means
  				// that it's in a scrollable area and now needs to have its position
  				// adjusted or recomputed, depending ont the repositionOnScroll
  				// option. Also, if the origin is partly hidden due to a parent that
  				// hides its overflow, we'll just hide (not close) the tooltip.
  				else {
  					
  					var g = self.__geometry(),
  						overflows = false;
  					
  					// a fixed position origin is not affected by the overflow hiding
  					// of a parent
  					if (self._$origin.css('position') != 'fixed') {
  						
  						self.__$originParents.each(function(i, el) {
  							
  							var $el = $(el),
  								overflowX = $el.css('overflow-x'),
  								overflowY = $el.css('overflow-y');
  							
  							if (overflowX != 'visible' || overflowY != 'visible') {
  								
  								var bcr = el.getBoundingClientRect();
  								
  								if (overflowX != 'visible') {
  									
  									if (	g.origin.windowOffset.left < bcr.left
  										||	g.origin.windowOffset.right > bcr.right
  									) {
  										overflows = true;
  										return false;
  									}
  								}
  								
  								if (overflowY != 'visible') {
  									
  									if (	g.origin.windowOffset.top < bcr.top
  										||	g.origin.windowOffset.bottom > bcr.bottom
  									) {
  										overflows = true;
  										return false;
  									}
  								}
  							}
  							
  							// no need to go further if fixed, for the same reason as above
  							if ($el.css('position') == 'fixed') {
  								return false;
  							}
  						});
  					}
  					
  					if (overflows) {
  						self._$tooltip.css('visibility', 'hidden');
  					}
  					else {
  						self._$tooltip.css('visibility', 'visible');
  						
  						// reposition
  						if (self.__options.repositionOnScroll) {
  							self.reposition(event);
  						}
  						// or just adjust offset
  						else {
  							
  							// we have to use offset and not windowOffset because this way,
  							// only the scroll distance of the scrollable areas are taken into
  							// account (the scrolltop value of the main window must be
  							// ignored since the tooltip already moves with it)
  							var offsetLeft = g.origin.offset.left - self.__Geometry.origin.offset.left,
  								offsetTop = g.origin.offset.top - self.__Geometry.origin.offset.top;
  							
  							// add the offset to the position initially computed by the display plugin
  							self._$tooltip.css({
  								left: self.__lastPosition.coord.left + offsetLeft,
  								top: self.__lastPosition.coord.top + offsetTop
  							});
  						}
  					}
  				}
  				
  				self._trigger({
  					type: 'scroll',
  					event: event
  				});
  			}
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * Changes the state of the tooltip
  	 *
  	 * @param {string} state
  	 * @returns {self}
  	 * @private
  	 */
  	__stateSet: function(state) {
  		
  		this.__state = state;
  		
  		this._trigger({
  			type: 'state',
  			state: state
  		});
  		
  		return this;
  	},
  	
  	/**
  	 * Clear appearance timeouts
  	 *
  	 * @returns {self}
  	 * @private
  	 */
  	__timeoutsClear: function() {
  		
  		// there is only one possible open timeout: the delayed opening
  		// when the mouseenter/touchstart open triggers are used
  		clearTimeout(this.__timeouts.open);
  		this.__timeouts.open = null;
  		
  		// ... but several close timeouts: the delayed closing when the
  		// mouseleave close trigger is used and the timer option
  		$.each(this.__timeouts.close, function(i, timeout) {
  			clearTimeout(timeout);
  		});
  		this.__timeouts.close = [];
  		
  		return this;
  	},
  	
  	/**
  	 * Start the tracker that will make checks at regular intervals
  	 * 
  	 * @returns {self}
  	 * @private
  	 */
  	__trackerStart: function() {
  		
  		var self = this,
  			$content = self._$tooltip.find('.tooltipster-content');
  		
  		// get the initial content size
  		if (self.__options.trackTooltip) {
  			self.__contentBcr = $content[0].getBoundingClientRect();
  		}
  		
  		self.__tracker = setInterval(function() {
  			
  			// if the origin or tooltip elements have been removed.
  			// Note: we could destroy the instance now if the origin has
  			// been removed but we'll leave that task to our garbage collector
  			if (!bodyContains(self._$origin) || !bodyContains(self._$tooltip)) {
  				self._close();
  			}
  			// if everything is alright
  			else {
  				
  				// compare the former and current positions of the origin to reposition
  				// the tooltip if need be
  				if (self.__options.trackOrigin) {
  					
  					var g = self.__geometry(),
  						identical = false;
  					
  					// compare size first (a change requires repositioning too)
  					if (areEqual(g.origin.size, self.__Geometry.origin.size)) {
  						
  						// for elements that have a fixed lineage (see __geometry()), we track the
  						// top and left properties (relative to window)
  						if (self.__Geometry.origin.fixedLineage) {
  							if (areEqual(g.origin.windowOffset, self.__Geometry.origin.windowOffset)) {
  								identical = true;
  							}
  						}
  						// otherwise, track total offset (relative to document)
  						else {
  							if (areEqual(g.origin.offset, self.__Geometry.origin.offset)) {
  								identical = true;
  							}
  						}
  					}
  					
  					if (!identical) {
  						
  						// close the tooltip when using the mouseleave close trigger
  						// (see https://github.com/iamceege/tooltipster/pull/253)
  						if (self.__options.triggerClose.mouseleave) {
  							self._close();
  						}
  						else {
  							self.reposition();
  						}
  					}
  				}
  				
  				if (self.__options.trackTooltip) {
  					
  					var currentBcr = $content[0].getBoundingClientRect();
  					
  					if (	currentBcr.height !== self.__contentBcr.height
  						||	currentBcr.width !== self.__contentBcr.width
  					) {
  						self.reposition();
  						self.__contentBcr = currentBcr;
  					}
  				}
  			}
  		}, self.__options.trackerInterval);
  		
  		return self;
  	},
  	
  	/**
  	 * Closes the tooltip (after the closing delay)
  	 * 
  	 * @param event
  	 * @param callback
  	 * @param force Set to true to override a potential refusal of the user's function
  	 * @returns {self}
  	 * @protected
  	 */
  	_close: function(event, callback, force) {
  		
  		var self = this,
  			ok = true;
  		
  		self._trigger({
  			type: 'close',
  			event: event,
  			stop: function() {
  				ok = false;
  			}
  		});
  		
  		// a destroying tooltip (force == true) may not refuse to close
  		if (ok || force) {
  			
  			// save the method custom callback and cancel any open method custom callbacks
  			if (callback) self.__callbacks.close.push(callback);
  			self.__callbacks.open = [];
  			
  			// clear open/close timeouts
  			self.__timeoutsClear();
  			
  			var finishCallbacks = function() {
  				
  				// trigger any close method custom callbacks and reset them
  				$.each(self.__callbacks.close, function(i,c) {
  					c.call(self, self, {
  						event: event,
  						origin: self._$origin[0]
  					});
  				});
  				
  				self.__callbacks.close = [];
  			};
  			
  			if (self.__state != 'closed') {
  				
  				var necessary = true,
  					d = new Date(),
  					now = d.getTime(),
  					newClosingTime = now + self.__options.animationDuration[1];
  				
  				// the tooltip may already already be disappearing, but if a new
  				// call to close() is made after the animationDuration was changed
  				// to 0 (for example), we ought to actually close it sooner than
  				// previously scheduled. In that case it should be noted that the
  				// browser will not adapt the animation duration to the new
  				// animationDuration that was set after the start of the closing
  				// animation.
  				// Note: the same thing could be considered at opening, but is not
  				// really useful since the tooltip is actually opened immediately
  				// upon a call to _open(). Since it would not make the opening
  				// animation finish sooner, its sole impact would be to trigger the
  				// state event and the open callbacks sooner than the actual end of
  				// the opening animation, which is not great.
  				if (self.__state == 'disappearing') {
  					
  					if (newClosingTime > self.__closingTime) {
  						necessary = false;
  					}
  				}
  				
  				if (necessary) {
  					
  					self.__closingTime = newClosingTime;
  					
  					if (self.__state != 'disappearing') {
  						self.__stateSet('disappearing');
  					}
  					
  					var finish = function() {
  						
  						// stop the tracker
  						clearInterval(self.__tracker);
  						
  						// a "beforeClose" option has been asked several times but would
  						// probably useless since the content element is still accessible
  						// via ::content(), and because people can always use listeners
  						// inside their content to track what's going on. For the sake of
  						// simplicity, this has been denied. Bur for the rare people who
  						// really need the option (for old browsers or for the case where
  						// detaching the content is actually destructive, for file or
  						// password inputs for example), this event will do the work.
  						self._trigger({
  							type: 'closing',
  							event: event
  						});
  						
  						// unbind listeners which are no longer needed
  						
  						self._$tooltip
  							.off('.'+ self.__namespace +'-triggerClose')
  							.removeClass('tooltipster-dying');
  						
  						// orientationchange, scroll and resize listeners
  						$(env.window).off('.'+ self.__namespace +'-triggerClose');
  						
  						// scroll listeners
  						self.__$originParents.each(function(i, el) {
  							$(el).off('scroll.'+ self.__namespace +'-triggerClose');
  						});
  						// clear the array to prevent memory leaks
  						self.__$originParents = null;
  						
  						$(env.window.document.body).off('.'+ self.__namespace +'-triggerClose');
  						
  						self._$origin.off('.'+ self.__namespace +'-triggerClose');
  						
  						self._off('dismissable');
  						
  						// a plugin that would like to remove the tooltip from the
  						// DOM when closed should bind on this
  						self.__stateSet('closed');
  						
  						// trigger event
  						self._trigger({
  							type: 'after',
  							event: event
  						});
  						
  						// call our constructor custom callback function
  						if (self.__options.functionAfter) {
  							self.__options.functionAfter.call(self, self, {
  								event: event,
  								origin: self._$origin[0]
  							});
  						}
  						
  						// call our method custom callbacks functions
  						finishCallbacks();
  					};
  					
  					if (env.hasTransitions) {
  						
  						self._$tooltip.css({
  							'-moz-animation-duration': self.__options.animationDuration[1] + 'ms',
  							'-ms-animation-duration': self.__options.animationDuration[1] + 'ms',
  							'-o-animation-duration': self.__options.animationDuration[1] + 'ms',
  							'-webkit-animation-duration': self.__options.animationDuration[1] + 'ms',
  							'animation-duration': self.__options.animationDuration[1] + 'ms',
  							'transition-duration': self.__options.animationDuration[1] + 'ms'
  						});
  						
  						self._$tooltip
  							// clear both potential open and close tasks
  							.clearQueue()
  							.removeClass('tooltipster-show')
  							// for transitions only
  							.addClass('tooltipster-dying');
  						
  						if (self.__options.animationDuration[1] > 0) {
  							self._$tooltip.delay(self.__options.animationDuration[1]);
  						}
  						
  						self._$tooltip.queue(finish);
  					}
  					else {
  						
  						self._$tooltip
  							.stop()
  							.fadeOut(self.__options.animationDuration[1], finish);
  					}
  				}
  			}
  			// if the tooltip is already closed, we still need to trigger
  			// the method custom callbacks
  			else {
  				finishCallbacks();
  			}
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * For internal use by plugins, if needed
  	 * 
  	 * @returns {self}
  	 * @protected
  	 */
  	_off: function() {
  		this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * For internal use by plugins, if needed
  	 *
  	 * @returns {self}
  	 * @protected
  	 */
  	_on: function() {
  		this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * For internal use by plugins, if needed
  	 *
  	 * @returns {self}
  	 * @protected
  	 */
  	_one: function() {
  		this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
  		return this;
  	},
  	
  	/**
  	 * Opens the tooltip right away.
  	 *
  	 * @param event
  	 * @param callback Will be called when the opening animation is over
  	 * @returns {self}
  	 * @protected
  	 */
  	_open: function(event, callback) {
  		
  		var self = this;
  		
  		// if the destruction process has not begun and if this was not
  		// triggered by an unwanted emulated click event
  		if (!self.__destroying) {
  			
  			// check that the origin is still in the DOM
  			if (	bodyContains(self._$origin)
  				// if the tooltip is enabled
  				&&	self.__enabled
  			) {
  				
  				var ok = true;
  				
  				// if the tooltip is not open yet, we need to call functionBefore.
  				// otherwise we can jst go on
  				if (self.__state == 'closed') {
  					
  					// trigger an event. The event.stop function allows the callback
  					// to prevent the opening of the tooltip
  					self._trigger({
  						type: 'before',
  						event: event,
  						stop: function() {
  							ok = false;
  						}
  					});
  					
  					if (ok && self.__options.functionBefore) {
  						
  						// call our custom function before continuing
  						ok = self.__options.functionBefore.call(self, self, {
  							event: event,
  							origin: self._$origin[0]
  						});
  					}
  				}
  				
  				if (ok !== false) {
  					
  					// if there is some content
  					if (self.__Content !== null) {
  						
  						// save the method callback and cancel close method callbacks
  						if (callback) {
  							self.__callbacks.open.push(callback);
  						}
  						self.__callbacks.close = [];
  						
  						// get rid of any appearance timeouts
  						self.__timeoutsClear();
  						
  						var extraTime,
  							finish = function() {
  								
  								if (self.__state != 'stable') {
  									self.__stateSet('stable');
  								}
  								
  								// trigger any open method custom callbacks and reset them
  								$.each(self.__callbacks.open, function(i,c) {
  									c.call(self, self, {
  										origin: self._$origin[0],
  										tooltip: self._$tooltip[0]
  									});
  								});
  								
  								self.__callbacks.open = [];
  							};
  						
  						// if the tooltip is already open
  						if (self.__state !== 'closed') {
  							
  							// the timer (if any) will start (or restart) right now
  							extraTime = 0;
  							
  							// if it was disappearing, cancel that
  							if (self.__state === 'disappearing') {
  								
  								self.__stateSet('appearing');
  								
  								if (env.hasTransitions) {
  									
  									self._$tooltip
  										.clearQueue()
  										.removeClass('tooltipster-dying')
  										.addClass('tooltipster-show');
  									
  									if (self.__options.animationDuration[0] > 0) {
  										self._$tooltip.delay(self.__options.animationDuration[0]);
  									}
  									
  									self._$tooltip.queue(finish);
  								}
  								else {
  									// in case the tooltip was currently fading out, bring it back
  									// to life
  									self._$tooltip
  										.stop()
  										.fadeIn(finish);
  								}
  							}
  							// if the tooltip is already open, we still need to trigger the method
  							// custom callback
  							else if (self.__state == 'stable') {
  								finish();
  							}
  						}
  						// if the tooltip isn't already open, open it
  						else {
  							
  							// a plugin must bind on this and store the tooltip in this._$tooltip
  							self.__stateSet('appearing');
  							
  							// the timer (if any) will start when the tooltip has fully appeared
  							// after its transition
  							extraTime = self.__options.animationDuration[0];
  							
  							// insert the content inside the tooltip
  							self.__contentInsert();
  							
  							// reposition the tooltip and attach to the DOM
  							self.reposition(event, true);
  							
  							// animate in the tooltip. If the display plugin wants no css
  							// animations, it may override the animation option with a
  							// dummy value that will produce no effect
  							if (env.hasTransitions) {
  								
  								// note: there seems to be an issue with start animations which
  								// are randomly not played on fast devices in both Chrome and FF,
  								// couldn't find a way to solve it yet. It seems that applying
  								// the classes before appending to the DOM helps a little, but
  								// it messes up some CSS transitions. The issue almost never
  								// happens when delay[0]==0 though
  								self._$tooltip
  									.addClass('tooltipster-'+ self.__options.animation)
  									.addClass('tooltipster-initial')
  									.css({
  										'-moz-animation-duration': self.__options.animationDuration[0] + 'ms',
  										'-ms-animation-duration': self.__options.animationDuration[0] + 'ms',
  										'-o-animation-duration': self.__options.animationDuration[0] + 'ms',
  										'-webkit-animation-duration': self.__options.animationDuration[0] + 'ms',
  										'animation-duration': self.__options.animationDuration[0] + 'ms',
  										'transition-duration': self.__options.animationDuration[0] + 'ms'
  									});
  								
  								setTimeout(
  									function() {
  										
  										// a quick hover may have already triggered a mouseleave
  										if (self.__state != 'closed') {
  											
  											self._$tooltip
  												.addClass('tooltipster-show')
  												.removeClass('tooltipster-initial');
  											
  											if (self.__options.animationDuration[0] > 0) {
  												self._$tooltip.delay(self.__options.animationDuration[0]);
  											}
  											
  											self._$tooltip.queue(finish);
  										}
  									},
  									0
  								);
  							}
  							else {
  								
  								// old browsers will have to live with this
  								self._$tooltip
  									.css('display', 'none')
  									.fadeIn(self.__options.animationDuration[0], finish);
  							}
  							
  							// checks if the origin is removed while the tooltip is open
  							self.__trackerStart();
  							
  							// NOTE: the listeners below have a '-triggerClose' namespace
  							// because we'll remove them when the tooltip closes (unlike
  							// the '-triggerOpen' listeners). So some of them are actually
  							// not about close triggers, rather about positioning.
  							
  							$(env.window)
  								// reposition on resize
  								.on('resize.'+ self.__namespace +'-triggerClose', function(e) {
  									
  									var $ae = $(document.activeElement);
  									
  									// reposition only if the resize event was not triggered upon the opening
  									// of a virtual keyboard due to an input field being focused within the tooltip
  									// (otherwise the repositioning would lose the focus)
  									if (	(!$ae.is('input') && !$ae.is('textarea'))
  										||	!$.contains(self._$tooltip[0], $ae[0])
  									) {
  										self.reposition(e);
  									}
  								})
  								// same as below for parents
  								.on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
  									self.__scrollHandler(e);
  								});
  							
  							self.__$originParents = self._$origin.parents();
  							
  							// scrolling may require the tooltip to be moved or even
  							// repositioned in some cases
  							self.__$originParents.each(function(i, parent) {
  								
  								$(parent).on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
  									self.__scrollHandler(e);
  								});
  							});
  							
  							if (	self.__options.triggerClose.mouseleave
  								||	(self.__options.triggerClose.touchleave && env.hasTouchCapability)
  							) {
  								
  								// we use an event to allow users/plugins to control when the mouseleave/touchleave
  								// close triggers will come to action. It allows to have more triggering elements
  								// than just the origin and the tooltip for example, or to cancel/delay the closing,
  								// or to make the tooltip interactive even if it wasn't when it was open, etc.
  								self._on('dismissable', function(event) {
  									
  									if (event.dismissable) {
  										
  										if (event.delay) {
  											
  											timeout = setTimeout(function() {
  												// event.event may be undefined
  												self._close(event.event);
  											}, event.delay);
  											
  											self.__timeouts.close.push(timeout);
  										}
  										else {
  											self._close(event);
  										}
  									}
  									else {
  										clearTimeout(timeout);
  									}
  								});
  								
  								// now set the listeners that will trigger 'dismissable' events
  								var $elements = self._$origin,
  									eventNamesIn = '',
  									eventNamesOut = '',
  									timeout = null;
  								
  								// if we have to allow interaction, bind on the tooltip too
  								if (self.__options.interactive) {
  									$elements = $elements.add(self._$tooltip);
  								}
  								
  								if (self.__options.triggerClose.mouseleave) {
  									eventNamesIn += 'mouseenter.'+ self.__namespace +'-triggerClose ';
  									eventNamesOut += 'mouseleave.'+ self.__namespace +'-triggerClose ';
  								}
  								if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
  									eventNamesIn += 'touchstart.'+ self.__namespace +'-triggerClose';
  									eventNamesOut += 'touchend.'+ self.__namespace +'-triggerClose touchcancel.'+ self.__namespace +'-triggerClose';
  								}
  								
  								$elements
  									// close after some time spent outside of the elements
  									.on(eventNamesOut, function(event) {
  										
  										// it's ok if the touch gesture ended up to be a swipe,
  										// it's still a "touch leave" situation
  										if (	self._touchIsTouchEvent(event)
  											||	!self._touchIsEmulatedEvent(event)
  										) {
  											
  											var delay = (event.type == 'mouseleave') ?
  												self.__options.delay :
  												self.__options.delayTouch;
  											
  											self._trigger({
  												delay: delay[1],
  												dismissable: true,
  												event: event,
  												type: 'dismissable'
  											});
  										}
  									})
  									// suspend the mouseleave timeout when the pointer comes back
  									// over the elements
  									.on(eventNamesIn, function(event) {
  										
  										// it's also ok if the touch event is a swipe gesture
  										if (	self._touchIsTouchEvent(event)
  											||	!self._touchIsEmulatedEvent(event)
  										) {
  											self._trigger({
  												dismissable: false,
  												event: event,
  												type: 'dismissable'
  											});
  										}
  									});
  							}
  							
  							// close the tooltip when the origin gets a mouse click (common behavior of
  							// native tooltips)
  							if (self.__options.triggerClose.originClick) {
  								
  								self._$origin.on('click.'+ self.__namespace + '-triggerClose', function(event) {
  									
  									// we could actually let a tap trigger this but this feature just
  									// does not make sense on touch devices
  									if (	!self._touchIsTouchEvent(event)
  										&&	!self._touchIsEmulatedEvent(event)
  									) {
  										self._close(event);
  									}
  								});
  							}
  							
  							// set the same bindings for click and touch on the body to close the tooltip
  							if (	self.__options.triggerClose.click
  								||	(self.__options.triggerClose.tap && env.hasTouchCapability)
  							) {
  								
  								// don't set right away since the click/tap event which triggered this method
  								// (if it was a click/tap) is going to bubble up to the body, we don't want it
  								// to close the tooltip immediately after it opened
  								setTimeout(function() {
  									
  									if (self.__state != 'closed') {
  										
  										var eventNames = '',
  											$body = $(env.window.document.body);
  										
  										if (self.__options.triggerClose.click) {
  											eventNames += 'click.'+ self.__namespace +'-triggerClose ';
  										}
  										if (self.__options.triggerClose.tap && env.hasTouchCapability) {
  											eventNames += 'touchend.'+ self.__namespace +'-triggerClose';
  										}
  										
  										$body.on(eventNames, function(event) {
  											
  											if (self._touchIsMeaningfulEvent(event)) {
  												
  												self._touchRecordEvent(event);
  												
  												if (!self.__options.interactive || !$.contains(self._$tooltip[0], event.target)) {
  													self._close(event);
  												}
  											}
  										});
  										
  										// needed to detect and ignore swiping
  										if (self.__options.triggerClose.tap && env.hasTouchCapability) {
  											
  											$body.on('touchstart.'+ self.__namespace +'-triggerClose', function(event) {
  												self._touchRecordEvent(event);
  											});
  										}
  									}
  								}, 0);
  							}
  							
  							self._trigger('ready');
  							
  							// call our custom callback
  							if (self.__options.functionReady) {
  								self.__options.functionReady.call(self, self, {
  									origin: self._$origin[0],
  									tooltip: self._$tooltip[0]
  								});
  							}
  						}
  						
  						// if we have a timer set, let the countdown begin
  						if (self.__options.timer > 0) {
  							
  							var timeout = setTimeout(function() {
  								self._close();
  							}, self.__options.timer + extraTime);
  							
  							self.__timeouts.close.push(timeout);
  						}
  					}
  				}
  			}
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * When using the mouseenter/touchstart open triggers, this function will
  	 * schedule the opening of the tooltip after the delay, if there is one
  	 *
  	 * @param event
  	 * @returns {self}
  	 * @protected
   	 */
  	_openShortly: function(event) {
  		
  		var self = this,
  			ok = true;
  		
  		if (self.__state != 'stable' && self.__state != 'appearing') {
  			
  			// if a timeout is not already running
  			if (!self.__timeouts.open) {
  				
  				self._trigger({
  					type: 'start',
  					event: event,
  					stop: function() {
  						ok = false;
  					}
  				});
  				
  				if (ok) {
  					
  					var delay = (event.type.indexOf('touch') == 0) ?
  						self.__options.delayTouch :
  						self.__options.delay;
  					
  					if (delay[0]) {
  						
  						self.__timeouts.open = setTimeout(function() {
  							
  							self.__timeouts.open = null;
  							
  							// open only if the pointer (mouse or touch) is still over the origin.
  							// The check on the "meaningful event" can only be made here, after some
  							// time has passed (to know if the touch was a swipe or not)
  							if (self.__pointerIsOverOrigin && self._touchIsMeaningfulEvent(event)) {
  								
  								// signal that we go on
  								self._trigger('startend');
  								
  								self._open(event);
  							}
  							else {
  								// signal that we cancel
  								self._trigger('startcancel');
  							}
  						}, delay[0]);
  					}
  					else {
  						// signal that we go on
  						self._trigger('startend');
  						
  						self._open(event);
  					}
  				}
  			}
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * Meant for plugins to get their options
  	 * 
  	 * @param {string} pluginName The name of the plugin that asks for its options
  	 * @param {object} defaultOptions The default options of the plugin
  	 * @returns {object} The options
  	 * @protected
  	 */
  	_optionsExtract: function(pluginName, defaultOptions) {
  		
  		var self = this,
  			options = $.extend(true, {}, defaultOptions);
  		
  		// if the plugin options were isolated in a property named after the
  		// plugin, use them (prevents conflicts with other plugins)
  		var pluginOptions = self.__options[pluginName];
  		
  		// if not, try to get them as regular options
  		if (!pluginOptions){
  			
  			pluginOptions = {};
  			
  			$.each(defaultOptions, function(optionName, value) {
  				
  				var o = self.__options[optionName];
  				
  				if (o !== undefined) {
  					pluginOptions[optionName] = o;
  				}
  			});
  		}
  		
  		// let's merge the default options and the ones that were provided. We'd want
  		// to do a deep copy but not let jQuery merge arrays, so we'll do a shallow
  		// extend on two levels, that will be enough if options are not more than 1
  		// level deep
  		$.each(options, function(optionName, value) {
  			
  			if (pluginOptions[optionName] !== undefined) {
  				
  				if ((		typeof value == 'object'
  						&&	!(value instanceof Array)
  						&&	value != null
  					)
  					&&
  					(		typeof pluginOptions[optionName] == 'object'
  						&&	!(pluginOptions[optionName] instanceof Array)
  						&&	pluginOptions[optionName] != null
  					)
  				) {
  					$.extend(options[optionName], pluginOptions[optionName]);
  				}
  				else {
  					options[optionName] = pluginOptions[optionName];
  				}
  			}
  		});
  		
  		return options;
  	},
  	
  	/**
  	 * Used at instantiation of the plugin, or afterwards by plugins that activate themselves
  	 * on existing instances
  	 * 
  	 * @param {object} pluginName
  	 * @returns {self}
  	 * @protected
  	 */
  	_plug: function(pluginName) {
  		
  		var plugin = $.tooltipster._plugin(pluginName);
  		
  		if (plugin) {
  			
  			// if there is a constructor for instances
  			if (plugin.instance) {
  				
  				// proxy non-private methods on the instance to allow new instance methods
  				$.tooltipster.__bridge(plugin.instance, this, plugin.name);
  			}
  		}
  		else {
  			throw new Error('The "'+ pluginName +'" plugin is not defined');
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * This will return true if the event is a mouse event which was
  	 * emulated by the browser after a touch event. This allows us to
  	 * really dissociate mouse and touch triggers.
  	 * 
  	 * There is a margin of error if a real mouse event is fired right
  	 * after (within the delay shown below) a touch event on the same
  	 * element, but hopefully it should not happen often.
  	 * 
  	 * @returns {boolean}
  	 * @protected
  	 */
  	_touchIsEmulatedEvent: function(event) {
  		
  		var isEmulated = false,
  			now = new Date().getTime();
  		
  		for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
  			
  			var e = this.__touchEvents[i];
  			
  			// delay, in milliseconds. It's supposed to be 300ms in
  			// most browsers (350ms on iOS) to allow a double tap but
  			// can be less (check out FastClick for more info)
  			if (now - e.time < 500) {
  				
  				if (e.target === event.target) {
  					isEmulated = true;
  				}
  			}
  			else {
  				break;
  			}
  		}
  		
  		return isEmulated;
  	},
  	
  	/**
  	 * Returns false if the event was an emulated mouse event or
  	 * a touch event involved in a swipe gesture.
  	 * 
  	 * @param {object} event
  	 * @returns {boolean}
  	 * @protected
  	 */
  	_touchIsMeaningfulEvent: function(event) {
  		return (
  				(this._touchIsTouchEvent(event) && !this._touchSwiped(event.target))
  			||	(!this._touchIsTouchEvent(event) && !this._touchIsEmulatedEvent(event))
  		);
  	},
  	
  	/**
  	 * Checks if an event is a touch event
  	 * 
  	 * @param {object} event
  	 * @returns {boolean}
  	 * @protected
  	 */
  	_touchIsTouchEvent: function(event){
  		return event.type.indexOf('touch') == 0;
  	},
  	
  	/**
  	 * Store touch events for a while to detect swiping and emulated mouse events
  	 * 
  	 * @param {object} event
  	 * @returns {self}
  	 * @protected
  	 */
  	_touchRecordEvent: function(event) {
  		
  		if (this._touchIsTouchEvent(event)) {
  			event.time = new Date().getTime();
  			this.__touchEvents.push(event);
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * Returns true if a swipe happened after the last touchstart event fired on
  	 * event.target.
  	 * 
  	 * We need to differentiate a swipe from a tap before we let the event open
  	 * or close the tooltip. A swipe is when a touchmove (scroll) event happens
  	 * on the body between the touchstart and the touchend events of an element.
  	 * 
  	 * @param {object} target The HTML element that may have triggered the swipe
  	 * @returns {boolean}
  	 * @protected
  	 */
  	_touchSwiped: function(target) {
  		
  		var swiped = false;
  		
  		for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
  			
  			var e = this.__touchEvents[i];
  			
  			if (e.type == 'touchmove') {
  				swiped = true;
  				break;
  			}
  			else if (
  				e.type == 'touchstart'
  				&&	target === e.target
  			) {
  				break;
  			}
  		}
  		
  		return swiped;
  	},
  	
  	/**
  	 * Triggers an event on the instance emitters
  	 * 
  	 * @returns {self}
  	 * @protected
  	 */
  	_trigger: function() {
  		
  		var args = Array.prototype.slice.apply(arguments);
  		
  		if (typeof args[0] == 'string') {
  			args[0] = { type: args[0] };
  		}
  		
  		// add properties to the event
  		args[0].instance = this;
  		args[0].origin = this._$origin ? this._$origin[0] : null;
  		args[0].tooltip = this._$tooltip ? this._$tooltip[0] : null;
  		
  		// note: the order of emitters matters
  		this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
  		$.tooltipster._trigger.apply($.tooltipster, args);
  		this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
  		
  		return this;
  	},
  	
  	/**
  	 * Deactivate a plugin on this instance
  	 * 
  	 * @returns {self}
  	 * @protected
  	 */
  	_unplug: function(pluginName) {
  		
  		var self = this;
  		
  		// if the plugin has been activated on this instance
  		if (self[pluginName]) {
  			
  			var plugin = $.tooltipster._plugin(pluginName);
  			
  			// if there is a constructor for instances
  			if (plugin.instance) {
  				
  				// unbridge
  				$.each(plugin.instance, function(methodName, fn) {
  					
  					// if the method exists (privates methods do not) and comes indeed from
  					// this plugin (may be missing or come from a conflicting plugin).
  					if (	self[methodName]
  						&&	self[methodName].bridged === self[pluginName]
  					) {
  						delete self[methodName];
  					}
  				});
  			}
  			
  			// destroy the plugin
  			if (self[pluginName].__destroy) {
  				self[pluginName].__destroy();
  			}
  			
  			// remove the reference to the plugin instance
  			delete self[pluginName];
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * @see self::_close
  	 * @returns {self}
  	 * @public
  	 */
  	close: function(callback) {
  		
  		if (!this.__destroyed) {
  			this._close(null, callback);
  		}
  		else {
  			this.__destroyError();
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * Sets or gets the content of the tooltip
  	 * 
  	 * @returns {mixed|self}
  	 * @public
  	 */
  	content: function(content) {
  		
  		var self = this;
  		
  		// getter method
  		if (content === undefined) {
  			return self.__Content;
  		}
  		// setter method
  		else {
  			
  			if (!self.__destroyed) {
  				
  				// change the content
  				self.__contentSet(content);
  				
  				if (self.__Content !== null) {
  					
  					// update the tooltip if it is open
  					if (self.__state !== 'closed') {
  						
  						// reset the content in the tooltip
  						self.__contentInsert();
  						
  						// reposition and resize the tooltip
  						self.reposition();
  						
  						// if we want to play a little animation showing the content changed
  						if (self.__options.updateAnimation) {
  							
  							if (env.hasTransitions) {
  								
  								// keep the reference in the local scope
  								var animation = self.__options.updateAnimation;
  								
  								self._$tooltip.addClass('tooltipster-update-'+ animation);
  								
  								// remove the class after a while. The actual duration of the
  								// update animation may be shorter, it's set in the CSS rules
  								setTimeout(function() {
  									
  									if (self.__state != 'closed') {
  										
  										self._$tooltip.removeClass('tooltipster-update-'+ animation);
  									}
  								}, 1000);
  							}
  							else {
  								self._$tooltip.fadeTo(200, 0.5, function() {
  									if (self.__state != 'closed') {
  										self._$tooltip.fadeTo(200, 1);
  									}
  								});
  							}
  						}
  					}
  				}
  				else {
  					self._close();
  				}
  			}
  			else {
  				self.__destroyError();
  			}
  			
  			return self;
  		}
  	},
  	
  	/**
  	 * Destroys the tooltip
  	 * 
  	 * @returns {self}
  	 * @public
  	 */
  	destroy: function() {
  		
  		var self = this;
  		
  		if (!self.__destroyed) {
  			
  			if(self.__state != 'closed'){
  				
  				// no closing delay
  				self.option('animationDuration', 0)
  					// force closing
  					._close(null, null, true);
  			}
  			
  			// send event
  			self._trigger('destroy');
  			
  			self.__destroyed = true;
  			
  			self._$origin
  				.removeData(self.__namespace)
  				// remove the open trigger listeners
  				.off('.'+ self.__namespace +'-triggerOpen');
  			
  			// remove the touch listener
  			$(env.window.document.body).off('.' + self.__namespace +'-triggerOpen');
  			
  			var ns = self._$origin.data('tooltipster-ns');
  			
  			// if the origin has been removed from DOM, its data may
  			// well have been destroyed in the process and there would
  			// be nothing to clean up or restore
  			if (ns) {
  				
  				// if there are no more tooltips on this element
  				if (ns.length === 1) {
  					
  					// optional restoration of a title attribute
  					var title = null;
  					if (self.__options.restoration == 'previous') {
  						title = self._$origin.data('tooltipster-initialTitle');
  					}
  					else if (self.__options.restoration == 'current') {
  						
  						// old school technique to stringify when outerHTML is not supported
  						title = (typeof self.__Content == 'string') ?
  							self.__Content :
  							$('<div></div>').append(self.__Content).html();
  					}
  					
  					if (title) {
  						self._$origin.attr('title', title);
  					}
  					
  					// final cleaning
  					
  					self._$origin.removeClass('tooltipstered');
  					
  					self._$origin
  						.removeData('tooltipster-ns')
  						.removeData('tooltipster-initialTitle');
  				}
  				else {
  					// remove the instance namespace from the list of namespaces of
  					// tooltips present on the element
  					ns = $.grep(ns, function(el, i) {
  						return el !== self.__namespace;
  					});
  					self._$origin.data('tooltipster-ns', ns);
  				}
  			}
  			
  			// last event
  			self._trigger('destroyed');
  			
  			// unbind private and public event listeners
  			self._off();
  			self.off();
  			
  			// remove external references, just in case
  			self.__Content = null;
  			self.__$emitterPrivate = null;
  			self.__$emitterPublic = null;
  			self.__options.parent = null;
  			self._$origin = null;
  			self._$tooltip = null;
  			
  			// make sure the object is no longer referenced in there to prevent
  			// memory leaks
  			$.tooltipster.__instancesLatestArr = $.grep($.tooltipster.__instancesLatestArr, function(el, i) {
  				return self !== el;
  			});
  			
  			clearInterval(self.__garbageCollector);
  		}
  		else {
  			self.__destroyError();
  		}
  		
  		// we return the scope rather than true so that the call to
  		// .tooltipster('destroy') actually returns the matched elements
  		// and applies to all of them
  		return self;
  	},
  	
  	/**
  	 * Disables the tooltip
  	 * 
  	 * @returns {self}
  	 * @public
  	 */
  	disable: function() {
  		
  		if (!this.__destroyed) {
  			
  			// close first, in case the tooltip would not disappear on
  			// its own (no close trigger)
  			this._close();
  			this.__enabled = false;
  			
  			return this;
  		}
  		else {
  			this.__destroyError();
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * Returns the HTML element of the origin
  	 *
  	 * @returns {self}
  	 * @public
  	 */
  	elementOrigin: function() {
  		
  		if (!this.__destroyed) {
  			return this._$origin[0];
  		}
  		else {
  			this.__destroyError();
  		}
  	},
  	
  	/**
  	 * Returns the HTML element of the tooltip
  	 *
  	 * @returns {self}
  	 * @public
  	 */
  	elementTooltip: function() {
  		return this._$tooltip ? this._$tooltip[0] : null;
  	},
  	
  	/**
  	 * Enables the tooltip
  	 * 
  	 * @returns {self}
  	 * @public
  	 */
  	enable: function() {
  		this.__enabled = true;
  		return this;
  	},
  	
  	/**
  	 * Alias, deprecated in 4.0.0
  	 * 
  	 * @param {function} callback
  	 * @returns {self}
  	 * @public
  	 */
  	hide: function(callback) {
  		return this.close(callback);
  	},
  	
  	/**
  	 * Returns the instance
  	 * 
  	 * @returns {self}
  	 * @public
  	 */
  	instance: function() {
  		return this;
  	},
  	
  	/**
  	 * For public use only, not to be used by plugins (use ::_off() instead)
  	 * 
  	 * @returns {self}
  	 * @public
  	 */
  	off: function() {
  		
  		if (!this.__destroyed) {
  			this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * For public use only, not to be used by plugins (use ::_on() instead)
  	 *
  	 * @returns {self}
  	 * @public
  	 */
  	on: function() {
  		
  		if (!this.__destroyed) {
  			this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		}
  		else {
  			this.__destroyError();
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * For public use only, not to be used by plugins
  	 *
  	 * @returns {self}
  	 * @public
  	 */
  	one: function() {
  		
  		if (!this.__destroyed) {
  			this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		}
  		else {
  			this.__destroyError();
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * @see self::_open
  	 * @returns {self}
  	 * @public
  	 */
  	open: function(callback) {
  		
  		if (!this.__destroyed) {
  			this._open(null, callback);
  		}
  		else {
  			this.__destroyError();
  		}
  		
  		return this;
  	},
  	
  	/**
  	 * Get or set options. For internal use and advanced users only.
  	 * 
  	 * @param {string} o Option name
  	 * @param {mixed} val optional A new value for the option
  	 * @return {mixed|self} If val is omitted, the value of the option
  	 * is returned, otherwise the instance itself is returned
  	 * @public
  	 */ 
  	option: function(o, val) {
  		
  		// getter
  		if (val === undefined) {
  			return this.__options[o];
  		}
  		// setter
  		else {
  			
  			if (!this.__destroyed) {
  				
  				// change value
  				this.__options[o] = val;
  				
  				// format
  				this.__optionsFormat();
  				
  				// re-prepare the triggers if needed
  				if ($.inArray(o, ['trigger', 'triggerClose', 'triggerOpen']) >= 0) {
  					this.__prepareOrigin();
  				}
  				
  				if (o === 'selfDestruction') {
  					this.__prepareGC();
  				}
  			}
  			else {
  				this.__destroyError();
  			}
  			
  			return this;
  		}
  	},
  	
  	/**
  	 * This method is in charge of setting the position and size properties of the tooltip.
  	 * All the hard work is delegated to the display plugin.
  	 * Note: The tooltip may be detached from the DOM at the moment the method is called 
  	 * but must be attached by the end of the method call.
  	 * 
  	 * @param {object} event For internal use only. Defined if an event such as
  	 * window resizing triggered the repositioning
  	 * @param {boolean} tooltipIsDetached For internal use only. Set this to true if you
  	 * know that the tooltip not being in the DOM is not an issue (typically when the
  	 * tooltip element has just been created but has not been added to the DOM yet).
  	 * @returns {self}
  	 * @public
  	 */
  	reposition: function(event, tooltipIsDetached) {
  		
  		var self = this;
  		
  		if (!self.__destroyed) {
  			
  			// if the tooltip is still open and the origin is still in the DOM
  			if (self.__state != 'closed' && bodyContains(self._$origin)) {
  				
  				// if the tooltip has not been removed from DOM manually (or if it
  				// has been detached on purpose)
  				if (tooltipIsDetached || bodyContains(self._$tooltip)) {
  					
  					if (!tooltipIsDetached) {
  						// detach in case the tooltip overflows the window and adds
  						// scrollbars to it, so __geometry can be accurate
  						self._$tooltip.detach();
  					}
  					
  					// refresh the geometry object before passing it as a helper
  					self.__Geometry = self.__geometry();
  					
  					// let a plugin fo the rest
  					self._trigger({
  						type: 'reposition',
  						event: event,
  						helper: {
  							geo: self.__Geometry
  						}
  					});
  				}
  			}
  		}
  		else {
  			self.__destroyError();
  		}
  		
  		return self;
  	},
  	
  	/**
  	 * Alias, deprecated in 4.0.0
  	 *
  	 * @param callback
  	 * @returns {self}
  	 * @public
  	 */
  	show: function(callback) {
  		return this.open(callback);
  	},
  	
  	/**
  	 * Returns some properties about the instance
  	 * 
  	 * @returns {object}
  	 * @public
  	 */
  	status: function() {
  		
  		return {
  			destroyed: this.__destroyed,
  			enabled: this.__enabled,
  			open: this.__state !== 'closed',
  			state: this.__state
  		};
  	},
  	
  	/**
  	 * For public use only, not to be used by plugins
  	 *
  	 * @returns {self}
  	 * @public
  	 */
  	triggerHandler: function() {
  		
  		if (!this.__destroyed) {
  			this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
  		}
  		else {
  			this.__destroyError();
  		}
  		
  		return this;
  	}
  };
  
  $.fn.tooltipster = function() {
  	
  	// for using in closures
  	var args = Array.prototype.slice.apply(arguments),
  		// common mistake: an HTML element can't be in several tooltips at the same time
  		contentCloningWarning = 'You are using a single HTML element as content for several tooltips. You probably want to set the contentCloning option to TRUE.';
  	
  	// this happens with $(sel).tooltipster(...) when $(sel) does not match anything
  	if (this.length === 0) {
  		
  		// still chainable
  		return this;
  	}
  	// this happens when calling $(sel).tooltipster('methodName or options')
  	// where $(sel) matches one or more elements
  	else {
  		
  		// method calls
  		if (typeof args[0] === 'string') {
  			
  			var v = '#*$~&';
  			
  			this.each(function() {
  				
  				// retrieve the namepaces of the tooltip(s) that exist on that element.
  				// We will interact with the first tooltip only.
  				var ns = $(this).data('tooltipster-ns'),
  					// self represents the instance of the first tooltipster plugin
  					// associated to the current HTML object of the loop
  					self = ns ? $(this).data(ns[0]) : null;
  				
  				// if the current element holds a tooltipster instance
  				if (self) {
  					
  					if (typeof self[args[0]] === 'function') {
  						
  						if (	this.length > 1
  							&&	args[0] == 'content'
  							&&	(	args[1] instanceof $
  								|| (typeof args[1] == 'object' && args[1] != null && args[1].tagName)
  							)
  							&&	!self.__options.contentCloning
  							&&	self.__options.debug
  						) {
  							console.log(contentCloningWarning);
  						}
  						
  						// note : args[1] and args[2] may not be defined
  						var resp = self[args[0]](args[1], args[2]);
  					}
  					else {
  						throw new Error('Unknown method "'+ args[0] +'"');
  					}
  					
  					// if the function returned anything other than the instance
  					// itself (which implies chaining, except for the `instance` method)
  					if (resp !== self || args[0] === 'instance') {
  						
  						v = resp;
  						
  						// return false to stop .each iteration on the first element
  						// matched by the selector
  						return false;
  					}
  				}
  				else {
  					throw new Error('You called Tooltipster\'s "'+ args[0] +'" method on an uninitialized element');
  				}
  			});
  			
  			return (v !== '#*$~&') ? v : this;
  		}
  		// first argument is undefined or an object: the tooltip is initializing
  		else {
  			
  			// reset the array of last initialized objects
  			$.tooltipster.__instancesLatestArr = [];
  			
  			// is there a defined value for the multiple option in the options object ?
  			var	multipleIsSet = args[0] && args[0].multiple !== undefined,
  				// if the multiple option is set to true, or if it's not defined but
  				// set to true in the defaults
  				multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
  				// same for content
  				contentIsSet = args[0] && args[0].content !== undefined,
  				content = (contentIsSet && args[0].content) || (!contentIsSet && defaults.content),
  				// same for contentCloning
  				contentCloningIsSet = args[0] && args[0].contentCloning !== undefined,
  				contentCloning =
  						(contentCloningIsSet && args[0].contentCloning)
  					||	(!contentCloningIsSet && defaults.contentCloning),
  				// same for debug
  				debugIsSet = args[0] && args[0].debug !== undefined,
  				debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
  			
  			if (	this.length > 1
  				&&	(	content instanceof $
  					|| (typeof content == 'object' && content != null && content.tagName)
  				)
  				&&	!contentCloning
  				&&	debug
  			) {
  				console.log(contentCloningWarning);
  			}
  			
  			// create a tooltipster instance for each element if it doesn't
  			// already have one or if the multiple option is set, and attach the
  			// object to it
  			this.each(function() {
  				
  				var go = false,
  					$this = $(this),
  					ns = $this.data('tooltipster-ns'),
  					obj = null;
  				
  				if (!ns) {
  					go = true;
  				}
  				else if (multiple) {
  					go = true;
  				}
  				else if (debug) {
  					console.log('Tooltipster: one or more tooltips are already attached to the element below. Ignoring.');
  					console.log(this);
  				}
  				
  				if (go) {
  					obj = new $.Tooltipster(this, args[0]);
  					
  					// save the reference of the new instance
  					if (!ns) ns = [];
  					ns.push(obj.__namespace);
  					$this.data('tooltipster-ns', ns);
  					
  					// save the instance itself
  					$this.data(obj.__namespace, obj);
  					
  					// call our constructor custom function.
  					// we do this here and not in ::init() because we wanted
  					// the object to be saved in $this.data before triggering
  					// it
  					if (obj.__options.functionInit) {
  						obj.__options.functionInit.call(obj, obj, {
  							origin: this
  						});
  					}
  					
  					// and now the event, for the plugins and core emitter
  					obj._trigger('init');
  				}
  				
  				$.tooltipster.__instancesLatestArr.push(obj);
  			});
  			
  			return this;
  		}
  	}
  };
  
  // Utilities
  
  /**
   * A class to check if a tooltip can fit in given dimensions
   * 
   * @param {object} $tooltip The jQuery wrapped tooltip element, or a clone of it
   */
  function Ruler($tooltip) {
  	
  	// list of instance variables
  	
  	this.$container;
  	this.constraints = null;
  	this.__$tooltip;
  	
  	this.__init($tooltip);
  }
  
  Ruler.prototype = {
  	
  	/**
  	 * Move the tooltip into an invisible div that does not allow overflow to make
  	 * size tests. Note: the tooltip may or may not be attached to the DOM at the
  	 * moment this method is called, it does not matter.
  	 * 
  	 * @param {object} $tooltip The object to test. May be just a clone of the
  	 * actual tooltip.
  	 * @private
  	 */
  	__init: function($tooltip) {
  		
  		this.__$tooltip = $tooltip;
  		
  		this.__$tooltip
  			.css({
  				// for some reason we have to specify top and left 0
  				left: 0,
  				// any overflow will be ignored while measuring
  				overflow: 'hidden',
  				// positions at (0,0) without the div using 100% of the available width
  				position: 'absolute',
  				top: 0
  			})
  			// overflow must be auto during the test. We re-set this in case
  			// it were modified by the user
  			.find('.tooltipster-content')
  				.css('overflow', 'auto');
  		
  		this.$container = $('<div class="tooltipster-ruler"></div>')
  			.append(this.__$tooltip)
  			.appendTo(env.window.document.body);
  	},
  	
  	/**
  	 * Force the browser to redraw (re-render) the tooltip immediately. This is required
  	 * when you changed some CSS properties and need to make something with it
  	 * immediately, without waiting for the browser to redraw at the end of instructions.
  	 *
  	 * @see http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes
  	 * @private
  	 */
  	__forceRedraw: function() {
  		
  		// note: this would work but for Webkit only
  		//this.__$tooltip.close();
  		//this.__$tooltip[0].offsetHeight;
  		//this.__$tooltip.open();
  		
  		// works in FF too
  		var $p = this.__$tooltip.parent();
  		this.__$tooltip.detach();
  		this.__$tooltip.appendTo($p);
  	},
  	
  	/**
  	 * Set maximum dimensions for the tooltip. A call to ::measure afterwards
  	 * will tell us if the content overflows or if it's ok
  	 *
  	 * @param {int} width
  	 * @param {int} height
  	 * @return {Ruler}
  	 * @public
  	 */
  	constrain: function(width, height) {
  		
  		this.constraints = {
  			width: width,
  			height: height
  		};
  		
  		this.__$tooltip.css({
  			// we disable display:flex, otherwise the content would overflow without
  			// creating horizontal scrolling (which we need to detect).
  			display: 'block',
  			// reset any previous height
  			height: '',
  			// we'll check if horizontal scrolling occurs
  			overflow: 'auto',
  			// we'll set the width and see what height is generated and if there
  			// is horizontal overflow
  			width: width
  		});
  		
  		return this;
  	},
  	
  	/**
  	 * Reset the tooltip content overflow and remove the test container
  	 * 
  	 * @returns {Ruler}
  	 * @public
  	 */
  	destroy: function() {
  		
  		// in case the element was not a clone
  		this.__$tooltip
  			.detach()
  			.find('.tooltipster-content')
  				.css({
  					// reset to CSS value
  					display: '',
  					overflow: ''
  				});
  		
  		this.$container.remove();
  	},
  	
  	/**
  	 * Removes any constraints
  	 * 
  	 * @returns {Ruler}
  	 * @public
  	 */
  	free: function() {
  		
  		this.constraints = null;
  		
  		// reset to natural size
  		this.__$tooltip.css({
  			display: '',
  			height: '',
  			overflow: 'visible',
  			width: ''
  		});
  		
  		return this;
  	},
  	
  	/**
  	 * Returns the size of the tooltip. When constraints are applied, also returns
  	 * whether the tooltip fits in the provided dimensions.
  	 * The idea is to see if the new height is small enough and if the content does
  	 * not overflow horizontally.
  	 *
  	 * @param {int} width
  	 * @param {int} height
  	 * @returns {object} An object with a bool `fits` property and a `size` property
  	 * @public
  	 */
  	measure: function() {
  		
  		this.__forceRedraw();
  		
  		var tooltipBcr = this.__$tooltip[0].getBoundingClientRect(),
  			result = { size: {
  				// bcr.width/height are not defined in IE8- but in this
  				// case, bcr.right/bottom will have the same value
  				// except in iOS 8+ where tooltipBcr.bottom/right are wrong
  				// after scrolling for reasons yet to be determined.
  				// tooltipBcr.top/left might not be 0, see issue #514
  				height: tooltipBcr.height || (tooltipBcr.bottom - tooltipBcr.top),
  				width: tooltipBcr.width || (tooltipBcr.right - tooltipBcr.left)
  			}};
  		
  		if (this.constraints) {
  			
  			// note: we used to use offsetWidth instead of boundingRectClient but
  			// it returned rounded values, causing issues with sub-pixel layouts.
  			
  			// note2: noticed that the bcrWidth of text content of a div was once
  			// greater than the bcrWidth of its container by 1px, causing the final
  			// tooltip box to be too small for its content. However, evaluating
  			// their widths one against the other (below) surprisingly returned
  			// equality. Happened only once in Chrome 48, was not able to reproduce
  			// => just having fun with float position values...
  			
  			var $content = this.__$tooltip.find('.tooltipster-content'),
  				height = this.__$tooltip.outerHeight(),
  				contentBcr = $content[0].getBoundingClientRect(),
  				fits = {
  					height: height <= this.constraints.height,
  					width: (
  						// this condition accounts for min-width property that
  						// may apply
  						tooltipBcr.width <= this.constraints.width
  							// the -1 is here because scrollWidth actually returns
  							// a rounded value, and may be greater than bcr.width if
  							// it was rounded up. This may cause an issue for contents
  							// which actually really overflow  by 1px or so, but that
  							// should be rare. Not sure how to solve this efficiently.
  							// See http://blogs.msdn.com/b/ie/archive/2012/02/17/sub-pixel-rendering-and-the-css-object-model.aspx
  						&&	contentBcr.width >= $content[0].scrollWidth - 1
  					)
  				};
  			
  			result.fits = fits.height && fits.width;
  		}
  		
  		// old versions of IE get the width wrong for some reason and it causes
  		// the text to be broken to a new line, so we round it up. If the width
  		// is the width of the screen though, we can assume it is accurate.
  		if (	env.IE
  			&&	env.IE <= 11
  			&&	result.size.width !== env.window.document.documentElement.clientWidth
  		) {
  			result.size.width = Math.ceil(result.size.width) + 1;
  		}
  		
  		return result;
  	}
  };
  
  // quick & dirty compare function, not bijective nor multidimensional
  function areEqual(a,b) {
  	var same = true;
  	$.each(a, function(i, _) {
  		if (b[i] === undefined || a[i] !== b[i]) {
  			same = false;
  			return false;
  		}
  	});
  	return same;
  }
  
  /**
   * A fast function to check if an element is still in the DOM. It
   * tries to use an id as ids are indexed by the browser, or falls
   * back to jQuery's `contains` method. May fail if two elements
   * have the same id, but so be it
   *
   * @param {object} $obj A jQuery-wrapped HTML element
   * @return {boolean}
   */
  function bodyContains($obj) {
  	var id = $obj.attr('id'),
  		el = id ? env.window.document.getElementById(id) : null;
  	// must also check that the element with the id is the one we want
  	return el ? el === $obj[0] : $.contains(env.window.document.body, $obj[0]);
  }
  
  // detect IE versions for dirty fixes
  var uA = navigator.userAgent.toLowerCase();
  if (uA.indexOf('msie') != -1) env.IE = parseInt(uA.split('msie')[1]);
  else if (uA.toLowerCase().indexOf('trident') !== -1 && uA.indexOf(' rv:11') !== -1) env.IE = 11;
  else if (uA.toLowerCase().indexOf('edge/') != -1) env.IE = parseInt(uA.toLowerCase().split('edge/')[1]);
  
  // detecting support for CSS transitions
  function transitionSupport() {
  	
  	// env.window is not defined yet when this is called
  	if (!win) return false;
  	
  	var b = win.document.body || win.document.documentElement,
  		s = b.style,
  		p = 'transition',
  		v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
  	
  	if (typeof s[p] == 'string') { return true; }
  	
  	p = p.charAt(0).toUpperCase() + p.substr(1);
  	for (var i=0; i<v.length; i++) {
  		if (typeof s[v[i] + p] == 'string') { return true; }
  	}
  	return false;
  }
  
  // we'll return jQuery for plugins not to have to declare it as a dependency,
  // but it's done by a build task since it should be included only once at the
  // end when we concatenate the main file with a pluginreturn $;
  
  }));
  