// jscs:disable
// jshint ignore: start

/*
 * qTip2 - Pretty powerful tooltips - v3.0.3
 * http://qtip2.com
 *
 * Copyright (c) 2016
 * Released under the MIT licenses
 * http://jquery.org/license
 *
 * Date: Wed May 11 2016 10:31 GMT+0100+0100
 * Plugins: tips modal viewport svg imagemap ie6
 * Styles: core basic css3
 */
/*global window: false, jQuery: false, console: false, define: false */

/* Cache window, document, undefined */
(function( window, document, undefined ) {

	// Uses AMD or browser globals to create a jQuery plugin.
	(function( factory ) {
		"use strict";
		if(typeof define === 'function' && define.amd) {
			define(['jquery'], factory);
		}
		else if(jQuery && !jQuery.fn.qtip) {
			factory(jQuery);
		}
	}
	(function($) {
		"use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
		;// Munge the primitives - Paul Irish tip
		var TRUE = true,
			FALSE = false,
			NULL = null,

			// Common variables
			X = 'x', Y = 'y',
			WIDTH = 'width',
			HEIGHT = 'height',

			// Positioning sides
			TOP = 'top',
			LEFT = 'left',
			BOTTOM = 'bottom',
			RIGHT = 'right',
			CENTER = 'center',

			// Position adjustment types
			FLIP = 'flip',
			FLIPINVERT = 'flipinvert',
			SHIFT = 'shift',

			// Shortcut vars
			QTIP, PROTOTYPE, CORNER, CHECKS,
			PLUGINS = {},
			NAMESPACE = 'qtip',
			ATTR_HAS = 'data-hasqtip',
			ATTR_ID = 'data-qtip-id',
			WIDGET = ['ui-widget', 'ui-tooltip'],
			SELECTOR = '.'+NAMESPACE,
			INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '),

			CLASS_FIXED = NAMESPACE+'-fixed',
			CLASS_DEFAULT = NAMESPACE + '-default',
			CLASS_FOCUS = NAMESPACE + '-focus',
			CLASS_HOVER = NAMESPACE + '-hover',
			CLASS_DISABLED = NAMESPACE+'-disabled',

			replaceSuffix = '_replacedByqTip',
			oldtitle = 'oldtitle',
			trackingBound,

			// Browser detection
			BROWSER = {
				/*
				 * IE version detection
				 *
				 * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
				 * Credit to James Padolsey for the original implemntation!
				 */
				ie: (function() {
					/* eslint-disable no-empty */
					var v, i;
					for (
						v = 4, i = document.createElement('div');
						(i.innerHTML = '<!--[if gt IE ' + v + ']><i></i><![endif]-->') && i.getElementsByTagName('i')[0];
						v+=1
					) {}
					return v > 4 ? v : NaN;
					/* eslint-enable no-empty */
				})(),

				/*
				 * iOS version detection
				 */
				iOS: parseFloat(
					('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
					.replace('undefined', '3_2').replace('_', '.').replace('_', '')
				) || FALSE
			};
		;function QTip(target, options, id, attr) {
			// Elements and ID
			this.id = id;
			this.target = target;
			this.tooltip = NULL;
			this.elements = { target: target };

			// Internal constructs
			this._id = NAMESPACE + '-' + id;
			this.timers = { img: {} };
			this.options = options;
			this.plugins = {};

			// Cache object
			this.cache = {
				event: {},
				target: $(),
				disabled: FALSE,
				attr: attr,
				onTooltip: FALSE,
				lastClass: ''
			};

			// Set the initial flags
			this.rendered = this.destroyed = this.disabled = this.waiting =
				this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
		}
		PROTOTYPE = QTip.prototype;

		PROTOTYPE._when = function(deferreds) {
			return $.when.apply($, deferreds);
		};

		PROTOTYPE.render = function(show) {
			if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit

			var self = this,
				options = this.options,
				cache = this.cache,
				elements = this.elements,
				text = options.content.text,
				title = options.content.title,
				button = options.content.button,
				posOptions = options.position,
				deferreds = [];

			// Add ARIA attributes to target
			$.attr(this.target[0], 'aria-describedby', this._id);

			// Create public position object that tracks current position corners
			cache.posClass = this._createPosClass(
				(this.position = { my: posOptions.my, at: posOptions.at }).my
			);

			// Create tooltip element
			this.tooltip = elements.tooltip = $('<div/>', {
				'id': this._id,
				'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '),
				'width': options.style.width || '',
				'height': options.style.height || '',
				'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,

				/* ARIA specific attributes */
				'role': 'alert',
				'aria-live': 'polite',
				'aria-atomic': FALSE,
				'aria-describedby': this._id + '-content',
				'aria-hidden': TRUE
			})
			.toggleClass(CLASS_DISABLED, this.disabled)
			.attr(ATTR_ID, this.id)
			.data(NAMESPACE, this)
			.appendTo(posOptions.container)
			.append(
				// Create content element
				elements.content = $('<div />', {
					'class': NAMESPACE + '-content',
					'id': this._id + '-content',
					'aria-atomic': TRUE
				})
			);

			// Set rendered flag and prevent redundant reposition calls for now
			this.rendered = -1;
			this.positioning = TRUE;

			// Create title...
			if(title) {
				this._createTitle();

				// Update title only if its not a callback (called in toggle if so)
				if( typeof title !== 'function' ) {
					deferreds.push( this._updateTitle(title, FALSE) );
				}
			}

			// Create button
			if(button) { this._createButton(); }

			// Set proper rendered flag and update content if not a callback function (called in toggle)
			if( typeof text !== 'function') {
				deferreds.push( this._updateContent(text, FALSE) );
			}
			this.rendered = TRUE;

			// Setup widget classes
			this._setWidget();

			// Initialize 'render' plugins
			$.each(PLUGINS, function(name) {
				var instance;
				if(this.initialize === 'render' && (instance = this(self))) {
					self.plugins[name] = instance;
				}
			});

			// Unassign initial events and assign proper events
			this._unassignEvents();
			this._assignEvents();

			// When deferreds have completed
			this._when(deferreds).then(function() {
				// tooltiprender event
				self._trigger('render');

				// Reset flags
				self.positioning = FALSE;

				// Show tooltip if not hidden during wait period
				if(!self.hiddenDuringWait && (options.show.ready || show)) {
					self.toggle(TRUE, cache.event, FALSE);
				}
				self.hiddenDuringWait = FALSE;
			});

			// Expose API
			QTIP.api[this.id] = this;

			return this;
		};

		PROTOTYPE.destroy = function(immediate) {
			// Set flag the signify destroy is taking place to plugins
			// and ensure it only gets destroyed once!
			if(this.destroyed) { return this.target; }

			function process() {
				if(this.destroyed) { return; }
				this.destroyed = TRUE;

				var target = this.target,
					title = target.attr(oldtitle),
					timer;

				// Destroy tooltip if rendered
				if(this.rendered) {
					this.tooltip.stop(1,0).find('*').remove().end().remove();
				}

				// Destroy all plugins
				$.each(this.plugins, function() {
					this.destroy && this.destroy();
				});

				// Clear timers
				for (timer in this.timers) {
					if (this.timers.hasOwnProperty(timer)) {
						clearTimeout(this.timers[timer]);
					}
				}

				// Remove api object and ARIA attributes
				target.removeData(NAMESPACE)
				.removeAttr(ATTR_ID)
				.removeAttr(ATTR_HAS)
				.removeAttr('aria-describedby');

				// Reset old title attribute if removed
				if(this.options.suppress && title) {
					target.attr('title', title).removeAttr(oldtitle);
				}

				// Remove qTip events associated with this API
				this._unassignEvents();

				// Remove ID from used id objects, and delete object references
				// for better garbage collection and leak protection
				this.options = this.elements = this.cache = this.timers =
					this.plugins = this.mouse = NULL;

				// Delete epoxsed API object
				delete QTIP.api[this.id];
			}

			// If an immediate destroy is needed
			if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) {
				this.tooltip.one('tooltiphidden', process.bind(this)) /*$.proxy(process, this)) */;
				!this.triggering && this.hide();
			}

			// If we're not in the process of hiding... process
			else { process.call(this); }

			return this.target;
		};
		;function invalidOpt(a) {
			return a === NULL || typeof a !== 'object';
		}

		function invalidContent(c) {
			return !( typeof c === 'function' ||
				c && c.attr ||
				c.length ||
				typeof(c) === 'object' && (c.jquery || c.then));
		}

		// Option object sanitizer
		function sanitizeOptions(opts) {
			var content, text, ajax, once;

			if(invalidOpt(opts)) { return FALSE; }

			if(invalidOpt(opts.metadata)) {
				opts.metadata = { type: opts.metadata };
			}

			if('content' in opts) {
				content = opts.content;

				if(invalidOpt(content) || content.jquery || content.done) {
					text = invalidContent(content) ? FALSE : content;
					content = opts.content = {
						text: text
					};
				}
				else { text = content.text; }

				// DEPRECATED - Old content.ajax plugin functionality
				// Converts it into the proper Deferred syntax
				if('ajax' in content) {
					ajax = content.ajax;
					once = ajax && ajax.once !== FALSE;
					delete content.ajax;

					content.text = function(event, api) {
						var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',

							deferred = $.ajax(
								$.extend({}, ajax, { context: api })
							)
							.then(ajax.success, NULL, ajax.error)
							.then(function(newContent) {
									if(newContent && once) { api.set('content.text', newContent); }
									return newContent;
								},
								function(xhr, status, error) {
									if(api.destroyed || xhr.status === 0) { return; }
									api.set('content.text', status + ': ' + error);
								});

						return !once ? (api.set('content.text', loading), deferred) : loading;
					};
				}

				if('title' in content) {
					if($.isPlainObject(content.title)) {
						content.button = content.title.button;
						content.title = content.title.text;
					}

					if(invalidContent(content.title || FALSE)) {
						content.title = FALSE;
					}
				}
			}

			if('position' in opts && invalidOpt(opts.position)) {
				opts.position = { my: opts.position, at: opts.position };
			}

			if('show' in opts && invalidOpt(opts.show)) {
				opts.show = opts.show.jquery ? { target: opts.show } :
					opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
			}

			if('hide' in opts && invalidOpt(opts.hide)) {
				opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
			}

			if('style' in opts && invalidOpt(opts.style)) {
				opts.style = { classes: opts.style };
			}

			// Sanitize plugin options
			$.each(PLUGINS, function() {
				this.sanitize && this.sanitize(opts);
			});

			return opts;
		}

		// Setup builtin .set() option checks
		CHECKS = PROTOTYPE.checks = {
			builtin: {
				// Core checks
				'^id$': function(obj, o, v, prev) {
					var id = v === TRUE ? QTIP.nextid : v,
						newId = NAMESPACE + '-' + id;

					if(id !== FALSE && id.length > 0 && !$('#'+newId).length) {
						this._id = newId;

						if(this.rendered) {
							this.tooltip[0].id = this._id;
							this.elements.content[0].id = this._id + '-content';
							this.elements.title[0].id = this._id + '-title';
						}
					}
					else { obj[o] = prev; }
				},
				'^prerender': function(obj, o, v) {
					v && !this.rendered && this.render(this.options.show.ready);
				},

				// Content checks
				'^content.text$': function(obj, o, v) {
					this._updateContent(v);
				},
				'^content.attr$': function(obj, o, v, prev) {
					if(this.options.content.text === this.target.attr(prev)) {
						this._updateContent( this.target.attr(v) );
					}
				},
				'^content.title$': function(obj, o, v) {
					// Remove title if content is null
					if(!v) { return this._removeTitle(); }

					// If title isn't already created, create it now and update
					v && !this.elements.title && this._createTitle();
					this._updateTitle(v);
				},
				'^content.button$': function(obj, o, v) {
					this._updateButton(v);
				},
				'^content.title.(text|button)$': function(obj, o, v) {
					this.set('content.'+o, v); // Backwards title.text/button compat
				},

				// Position checks
				'^position.(my|at)$': function(obj, o, v){
					if('string' === typeof v) {
						this.position[o] = obj[o] = new CORNER(v, o === 'at');
					}
				},
				'^position.container$': function(obj, o, v){
					this.rendered && this.tooltip.appendTo(v);
				},

				// Show checks
				'^show.ready$': function(obj, o, v) {
					v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
				},

				// Style checks
				'^style.classes$': function(obj, o, v, p) {
					this.rendered && this.tooltip.removeClass(p).addClass(v);
				},
				'^style.(width|height)': function(obj, o, v) {
					this.rendered && this.tooltip.css(o, v);
				},
				'^style.widget|content.title': function() {
					this.rendered && this._setWidget();
				},
				'^style.def': function(obj, o, v) {
					this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
				},

				// Events check
				'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
					this.rendered && this.tooltip[( typeof v === 'function' ? '' : 'un') + 'bind']('tooltip'+o, v);
				},

				// Properties which require event reassignment
				'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
					if(!this.rendered) { return; }

					// Set tracking flag
					var posOptions = this.options.position;
					this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);

					// Reassign events
					this._unassignEvents();
					this._assignEvents();
				}
			}
		};

		// Dot notation converter
		function convertNotation(options, notation) {
			var i = 0, obj, option = options,

				// Split notation into array
				levels = notation.split('.');

			// Loop through
			while(option = option[ levels[i++] ]) {
				if(i < levels.length) { obj = option; }
			}

			return [obj || options, levels.pop()];
		}

		PROTOTYPE.get = function(notation) {
			if(this.destroyed) { return this; }

			var o = convertNotation(this.options, notation.toLowerCase()),
				result = o[0][ o[1] ];

			return result.precedance ? result.string() : result;
		};

		function setCallback(notation, args) {
			var category, rule, match;

			for(category in this.checks) {
				if (!this.checks.hasOwnProperty(category)) { continue; }

				for(rule in this.checks[category]) {
					if (!this.checks[category].hasOwnProperty(rule)) { continue; }

					if(match = (new RegExp(rule, 'i')).exec(notation)) {
						args.push(match);

						if(category === 'builtin' || this.plugins[category]) {
							this.checks[category][rule].apply(
								this.plugins[category] || this, args
							);
						}
					}
				}
			}
		}

		var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
			rrender = /^prerender|show\.ready/i;

		PROTOTYPE.set = function(option, value) {
			if(this.destroyed) { return this; }

			var rendered = this.rendered,
				reposition = FALSE,
				options = this.options,
				name;

			// Convert singular option/value pair into object form
			if('string' === typeof option) {
				name = option; option = {}; option[name] = value;
			}
			else { option = $.extend({}, option); }

			// Set all of the defined options to their new values
			$.each(option, function(notation, val) {
				if(rendered && rrender.test(notation)) {
					delete option[notation]; return;
				}

				// Set new obj value
				var obj = convertNotation(options, notation.toLowerCase()), previous;
				previous = obj[0][ obj[1] ];
				obj[0][ obj[1] ] = val && val.nodeType ? $(val) : val;

				// Also check if we need to reposition
				reposition = rmove.test(notation) || reposition;

				// Set the new params for the callback
				option[notation] = [obj[0], obj[1], val, previous];
			});

			// Re-sanitize options
			sanitizeOptions(options);

			/*
			 * Execute any valid callbacks for the set options
			 * Also set positioning flag so we don't get loads of redundant repositioning calls.
			 */
			this.positioning = TRUE;
			$.each(option, setCallback.bind(this) /* $.proxy(setCallback, this) */);
			this.positioning = FALSE;

			// Update position if needed
			if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
				this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
			}

			return this;
		};
		;PROTOTYPE._update = function(content, element) {
			var self = this,
				cache = this.cache;

			// Make sure tooltip is rendered and content is defined. If not return
			if(!this.rendered || !content) { return FALSE; }

			// Use function to parse content
			if( typeof content === 'function') {
				content = content.call(this.elements.target, cache.event, this) || '';
			}

			// Handle deferred content
			if( typeof content.then === 'function' ) {
				cache.waiting = TRUE;
				return content.then(function(c) {
					cache.waiting = FALSE;
					return self._update(c, element);
				}, NULL, function(e) {
					return self._update(e, element);
				});
			}

			// If content is null... return false
			if(content === FALSE || !content && content !== '') { return FALSE; }

			// Append new content if its a DOM array and show it if hidden
			if(content.jquery && content.length > 0) {
				element.empty().append(
					content.css({ display: 'block', visibility: 'visible' })
				);
			}

			// Content is a regular string, insert the new content
			else { element.html(content); }

			// Wait for content to be loaded, and reposition
			return this._waitForContent(element).then(function(images) {
				if(self.rendered && self.tooltip[0].offsetWidth > 0) {
					self.reposition(cache.event, !images.length);
				}
			});
		};

		PROTOTYPE._waitForContent = function(element) {
			var cache = this.cache;

			// Set flag
			cache.waiting = TRUE;

			// If imagesLoaded is included, ensure images have loaded and return promise
			return ( $.fn.imagesLoaded ? element.imagesLoaded() : new $.Deferred().resolve([]) )
			.done(function() { cache.waiting = FALSE; })
			.promise();
		};

		PROTOTYPE._updateContent = function(content, reposition) {
			this._update(content, this.elements.content, reposition);
		};

		PROTOTYPE._updateTitle = function(content, reposition) {
			if(this._update(content, this.elements.title, reposition) === FALSE) {
				this._removeTitle(FALSE);
			}
		};

		PROTOTYPE._createTitle = function()
		{
			var elements = this.elements,
				id = this._id+'-title';

			// Destroy previous title element, if present
			if(elements.titlebar) { this._removeTitle(); }

			// Create title bar and title elements
			elements.titlebar = $('<div />', {
				'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '')
			})
			.append(
				elements.title = $('<div />', {
					'id': id,
					'class': NAMESPACE + '-title',
					'aria-atomic': TRUE
				})
			)
			.insertBefore(elements.content)

			// Button-specific events
			.on('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
				$(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
			})
			.on('.qtip-close', 'mouseover mouseout', function(event){
				$(this).toggleClass('ui-state-hover', event.type === 'mouseover');
			});

			// Create button if enabled
			if(this.options.content.button) { this._createButton(); }
		};

		PROTOTYPE._removeTitle = function(reposition)
		{
			var elements = this.elements;

			if(elements.title) {
				elements.titlebar.remove();
				elements.titlebar = elements.title = elements.button = NULL;

				// Reposition if enabled
				if(reposition !== FALSE) { this.reposition(); }
			}
		};
		;PROTOTYPE._createPosClass = function(my) {
			return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev();
		};

		PROTOTYPE.reposition = function(event, effect) {
			if(!this.rendered || this.positioning || this.destroyed) { return this; }

			// Set positioning flag
			this.positioning = TRUE;

			var cache = this.cache,
				tooltip = this.tooltip,
				posOptions = this.options.position,
				target = posOptions.target,
				my = posOptions.my,
				at = posOptions.at,
				viewport = posOptions.viewport,
				container = posOptions.container,
				adjust = posOptions.adjust,
				method = adjust.method.split(' '),
				tooltipWidth = tooltip.outerWidth(FALSE),
				tooltipHeight = tooltip.outerHeight(FALSE),
				targetWidth = 0,
				targetHeight = 0,
				type = tooltip.css('position'),
				position = { left: 0, top: 0 },
				visible = tooltip[0].offsetWidth > 0,
				isScroll = event && event.type === 'scroll',
				win = $(window),
				doc = container[0].ownerDocument,
				mouse = this.mouse,
				pluginCalculations, offset, adjusted, newClass;

			// Check if absolute position was passed
			if(Array.isArray(target) && target.length === 2) {
				// Force left top and set position
				at = { x: LEFT, y: TOP };
				position = { left: target[0], top: target[1] };
			}

			// Check if mouse was the target
			else if(target === 'mouse') {
				// Force left top to allow flipping
				at = { x: LEFT, y: TOP };

				// Use the mouse origin that caused the show event, if distance hiding is enabled
				if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) {
					event =  cache.origin;
				}

				// Use cached event for resize/scroll events
				else if(!event || event && (event.type === 'resize' || event.type === 'scroll')) {
					event = cache.event;
				}

				// Otherwise, use the cached mouse coordinates if available
				else if(mouse && mouse.pageX) {
					event = mouse;
				}

				// Calculate body and container offset and take them into account below
				if(type !== 'static') { position = container.offset(); }
				if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) {
					offset = $(document.body).offset();
				}

				// Use event coordinates for position
				position = {
					left: event.pageX - position.left + (offset && offset.left || 0),
					top: event.pageY - position.top + (offset && offset.top || 0)
				};

				// Scroll events are a pain, some browsers
				if(adjust.mouse && isScroll && mouse) {
					position.left -= (mouse.scrollX || 0) - win.scrollLeft();
					position.top -= (mouse.scrollY || 0) - win.scrollTop();
				}
			}

			// Target wasn't mouse or absolute...
			else {
				// Check if event targetting is being used
				if(target === 'event') {
					if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
						cache.target = $(event.target);
					}
					else if(!event.target) {
						cache.target = this.elements.target;
					}
				}
				else if(target !== 'event'){
					cache.target = $(target.jquery ? target : this.elements.target);
				}
				target = cache.target;

				// Parse the target into a jQuery object and make sure there's an element present
				target = $(target).eq(0);
				if(target.length === 0) { return this; }

				// Check if window or document is the target
				else if(target[0] === document || target[0] === window) {
					targetWidth = BROWSER.iOS ? window.innerWidth : target.width();
					targetHeight = BROWSER.iOS ? window.innerHeight : target.height();

					if(target[0] === window) {
						position = {
							top: (viewport || target).scrollTop(),
							left: (viewport || target).scrollLeft()
						};
					}
				}

				// Check if the target is an <AREA> element
				else if(PLUGINS.imagemap && target.is('area')) {
					pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE);
				}

				// Check if the target is an SVG element
				else if(PLUGINS.svg && target && target[0].ownerSVGElement) {
					pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);
				}

				// Otherwise use regular jQuery methods
				else {
					targetWidth = target.outerWidth(FALSE);
					targetHeight = target.outerHeight(FALSE);
					position = target.offset();
				}

				// Parse returned plugin values into proper variables
				if(pluginCalculations) {
					targetWidth = pluginCalculations.width;
					targetHeight = pluginCalculations.height;
					offset = pluginCalculations.offset;
					position = pluginCalculations.position;
				}

				// Adjust position to take into account offset parents
				position = this.reposition.offset(target, position, container);

				// Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
				if(BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1 ||
					BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33 ||
					!BROWSER.iOS && type === 'fixed'
				){
					position.left -= win.scrollLeft();
					position.top -= win.scrollTop();
				}

				// Adjust position relative to target
				if(!pluginCalculations || pluginCalculations && pluginCalculations.adjustable !== FALSE) {
					position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
					position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
				}
			}

			// Adjust position relative to tooltip
			position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0);
			position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0);

			// Use viewport adjustment plugin if enabled
			if(PLUGINS.viewport) {
				adjusted = position.adjusted = PLUGINS.viewport(
					this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight
				);

				// Apply offsets supplied by positioning plugin (if used)
				if(offset && adjusted.left) { position.left += offset.left; }
				if(offset && adjusted.top) {  position.top += offset.top; }

				// Apply any new 'my' position
				if(adjusted.my) { this.position.my = adjusted.my; }
			}

			// Viewport adjustment is disabled, set values to zero
			else { position.adjusted = { left: 0, top: 0 }; }

			// Set tooltip position class if it's changed
			if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) {
				cache.posClass = newClass;
				tooltip.removeClass(cache.posClass).addClass(newClass);
			}

			// tooltipmove event
			if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }
			delete position.adjusted;

			// If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
			if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || typeof posOptions.effect !== 'function' ) {
				tooltip.css(position);
			}

			// Use custom function if provided
			else if( typeof posOptions.effect === 'function') {
				posOptions.effect.call(tooltip, this, $.extend({}, position));
				tooltip.queue(function(next) {
					// Reset attributes to avoid cross-browser rendering bugs
					$(this).css({ opacity: '', height: '' });
					if(BROWSER.ie) { this.style.removeAttribute('filter'); }

					next();
				});
			}

			// Set positioning flag
			this.positioning = FALSE;

			return this;
		};

		// Custom (more correct for qTip!) offset calculator
		PROTOTYPE.reposition.offset = function(elem, pos, container) {
			if(!container[0]) { return pos; }

			var ownerDocument = $(elem[0].ownerDocument),
				quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat',
				parent = container[0],
				scrolled, position, parentOffset, overflow;

			function scroll(e, i) {
				pos.left += i * e.scrollLeft();
				pos.top += i * e.scrollTop();
			}

			// Compensate for non-static containers offset
			do {
				if((position = $.css(parent, 'position')) !== 'static') {
					if(position === 'fixed') {
						parentOffset = parent.getBoundingClientRect();
						scroll(ownerDocument, -1);
					}
					else {
						parentOffset = $(parent).position();
						parentOffset.left += parseFloat($.css(parent, 'borderLeftWidth')) || 0;
						parentOffset.top += parseFloat($.css(parent, 'borderTopWidth')) || 0;
					}

					pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0);
					pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0);

					// If this is the first parent element with an overflow of "scroll" or "auto", store it
					if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); }
				}
			}
			while(parent = parent.offsetParent);

			// Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
			if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {
				scroll(scrolled, 1);
			}

			return pos;
		};

		// Corner class
		var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {
			corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
			this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
			this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
			this.forceY = !!forceY;

			var f = corner.charAt(0);
			this.precedance = f === 't' || f === 'b' ? Y : X;
		}).prototype;

		C.invert = function(z, center) {
			this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
		};

		C.string = function(join) {
			var x = this.x, y = this.y;

			var result = x !== y ?
				x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ?
					[y,x] :
					[x,y] :
				[x];

			return join !== false ? result.join(' ') : result;
		};

		C.abbrev = function() {
			var result = this.string(false);
			return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');
		};

		C.clone = function() {
			return new CORNER( this.string(), this.forceY );
		};

		;
		PROTOTYPE.toggle = function(state, event) {
			var cache = this.cache,
				options = this.options,
				tooltip = this.tooltip;

			// Try to prevent flickering when tooltip overlaps show element
			if(event) {
				if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) &&
					options.show.target.add(event.target).length === options.show.target.length &&
					tooltip.has(event.relatedTarget).length) {
					return this;
				}

				// Cache event
				cache.event = $.event.fix(event);
			}

			// If we're currently waiting and we've just hidden... stop it
			this.waiting && !state && (this.hiddenDuringWait = TRUE);

			// Render the tooltip if showing and it isn't already
			if(!this.rendered) { return state ? this.render(1) : this; }
			else if(this.destroyed || this.disabled) { return this; }

			var type = state ? 'show' : 'hide',
				opts = this.options[type],
				posOptions = this.options.position,
				contentOptions = this.options.content,
				width = this.tooltip.css('width'),
				visible = this.tooltip.is(':visible'),
				animate = state || opts.target.length === 1,
				sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
				identicalState, allow, after;

			// Detect state if valid one isn't provided
			if((typeof state).search('boolean|number')) { state = !visible; }

			// Check if the tooltip is in an identical state to the new would-be state
			identicalState = !tooltip.is(':animated') && visible === state && sameTarget;

			// Fire tooltip(show/hide) event and check if destroyed
			allow = !identicalState ? !!this._trigger(type, [90]) : NULL;

			// Check to make sure the tooltip wasn't destroyed in the callback
			if(this.destroyed) { return this; }

			// If the user didn't stop the method prematurely and we're showing the tooltip, focus it
			if(allow !== FALSE && state) { this.focus(event); }

			// If the state hasn't changed or the user stopped it, return early
			if(!allow || identicalState) { return this; }

			// Set ARIA hidden attribute
			$.attr(tooltip[0], 'aria-hidden', !!!state);

			// Execute state specific properties
			if(state) {
				// Store show origin coordinates
				this.mouse && (cache.origin = $.event.fix(this.mouse));

				// Update tooltip content & title if it's a dynamic function
				if(typeof contentOptions.text === 'function') { this._updateContent(contentOptions.text, FALSE); }
				if(typeof contentOptions.title === 'function') { this._updateTitle(contentOptions.title, FALSE); }

				// Cache mousemove events for positioning purposes (if not already tracking)
				if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
					$(document).on('mousemove.'+NAMESPACE, this._storeMouse);
					trackingBound = TRUE;
				}

				// Update the tooltip position (set width first to prevent viewport/max-width issues)
				if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); }
				this.reposition(event, arguments[2]);
				if(!width) { tooltip.css('width', ''); }

				// Hide other tooltips if tooltip is solo
				if(!!opts.solo) {
					(typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo))
					.not(tooltip).not(opts.target).qtip('hide', new $.Event('tooltipsolo'));
				}
			}
			else {
				// Clear show timer if we're hiding
				clearTimeout(this.timers.show);

				// Remove cached origin on hide
				delete cache.origin;

				// Remove mouse tracking event if not needed (all tracking qTips are hidden)
				if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
					$(document).off('mousemove.'+NAMESPACE);
					trackingBound = FALSE;
				}

				// Blur the tooltip
				this.blur(event);
			}

			// Define post-animation, state specific properties
			after = function() {
				if (state) {
					// Prevent antialias from disappearing in IE by removing filter
					if (BROWSER.ie) {
						tooltip[0].style.removeAttribute('filter');
					}

					// Remove overflow setting to prevent tip bugs
					tooltip.css('overflow', '');

					// Autofocus elements if enabled
					if ('string' === typeof opts.autofocus) {
						$(this.options.show.autofocus, tooltip).focus();
					}

					// If set, hide tooltip when inactive for delay period
					this.options.show.target.trigger('qtip-' + this.id + '-inactive');
				} else {
					// Reset CSS states
					tooltip.css({
						display: '',
						visibility: '',
						opacity: '',
						left: '',
						top: ''
					});
				}

				// tooltipvisible/tooltiphidden events
				this._trigger(state ? 'visible' : 'hidden');
			}.bind(this);

			// If no effect type is supplied, use a simple toggle
			if(opts.effect === FALSE || animate === FALSE) {
				tooltip[ type ]();
				after();
			}

			// Use custom function if provided
			else if( typeof opts.effect === 'function' ) {
				tooltip.stop(1, 1);
				opts.effect.call(tooltip, this);
				tooltip.queue('fx', function(n) {
					after(); n();
				});
			}

			// Use basic fade function by default
			else { tooltip.fadeTo(90, state ? 1 : 0, after); }

			// If inactive hide method is set, active it
			if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); }

			return this;
		};

		PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };

		PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };
		;PROTOTYPE.focus = function(event) {
			if(!this.rendered || this.destroyed) { return this; }

			var qtips = $(SELECTOR),
				tooltip = this.tooltip,
				curIndex = parseInt(tooltip[0].style.zIndex, 10),
				newIndex = QTIP.zindex + qtips.length;

			// Only update the z-index if it has changed and tooltip is not already focused
			if(!tooltip.hasClass(CLASS_FOCUS)) {
				// tooltipfocus event
				if(this._trigger('focus', [newIndex], event)) {
					// Only update z-index's if they've changed
					if(curIndex !== newIndex) {
						// Reduce our z-index's and keep them properly ordered
						qtips.each(function() {
							if(this.style.zIndex > curIndex) {
								this.style.zIndex = this.style.zIndex - 1;
							}
						});

						// Fire blur event for focused tooltip
						qtips.filter('.' + CLASS_FOCUS).qtip('blur', event);
					}

					// Set the new z-index
					tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
				}
			}

			return this;
		};

		PROTOTYPE.blur = function(event) {
			if(!this.rendered || this.destroyed) { return this; }

			// Set focused status to FALSE
			this.tooltip.removeClass(CLASS_FOCUS);

			// tooltipblur event
			this._trigger('blur', [ this.tooltip.css('zIndex') ], event);

			return this;
		};
		;PROTOTYPE.disable = function(state) {
			if(this.destroyed) { return this; }

			// If 'toggle' is passed, toggle the current state
			if(state === 'toggle') {
				state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled);
			}

			// Disable if no state passed
			else if('boolean' !== typeof state) {
				state = TRUE;
			}

			if(this.rendered) {
				this.tooltip.toggleClass(CLASS_DISABLED, state)
				.attr('aria-disabled', state);
			}

			this.disabled = !!state;

			return this;
		};

		PROTOTYPE.enable = function() { return this.disable(FALSE); };
		;PROTOTYPE._createButton = function()
		{
			var self = this,
				elements = this.elements,
				tooltip = elements.tooltip,
				button = this.options.content.button,
				isString = typeof button === 'string',
				close = isString ? button : 'Close tooltip';

			if(elements.button) { elements.button.remove(); }

			// Use custom button if one was supplied by user, else use default
			if(button.jquery) {
				elements.button = button;
			}
			else {
				elements.button = $('<a />', {
					'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'),
					'title': close,
					'aria-label': close
				})
				.prepend(
					$('<span />', {
						'class': 'ui-icon ui-icon-close',
						'html': '&times;'
					})
				);
			}

			// Create button and setup attributes
			elements.button.appendTo(elements.titlebar || tooltip)
			.attr('role', 'button')
			.on( 'click', function(event) {
				if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); }
				return FALSE;
			});
		};

		PROTOTYPE._updateButton = function(button)
		{
			// Make sure tooltip is rendered and if not, return
			if(!this.rendered) { return FALSE; }

			var elem = this.elements.button;
			if(button) { this._createButton(); }
			else { elem.remove(); }
		};
		;// Widget class creator
		function createWidgetClass(cls) {
			return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');
		}

		// Widget class setter method
		PROTOTYPE._setWidget = function()
		{
			var on = this.options.style.widget,
				elements = this.elements,
				tooltip = elements.tooltip,
				disabled = tooltip.hasClass(CLASS_DISABLED);

			tooltip.removeClass(CLASS_DISABLED);
			CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled';
			tooltip.toggleClass(CLASS_DISABLED, disabled);

			tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);

			if(elements.content) {
				elements.content.toggleClass( createWidgetClass('content'), on);
			}
			if(elements.titlebar) {
				elements.titlebar.toggleClass( createWidgetClass('header'), on);
			}
			if(elements.button) {
				elements.button.toggleClass(NAMESPACE+'-icon', !on);
			}
		};
		;function delay(callback, duration) {
			// If tooltip has displayed, start hide timer
			if(duration > 0) {
				return setTimeout(
					//$.proxy(callback, this), duration
					callback.bind(this), duration
				);
			}
			else{ callback.call(this); }
		}

		function showMethod(event) {
			if(this.tooltip.hasClass(CLASS_DISABLED)) { return; }

			// Clear hide timers
			clearTimeout(this.timers.show);
			clearTimeout(this.timers.hide);

			// Start show timer
			this.timers.show = delay.call(this,
				function() { this.toggle(TRUE, event); },
				this.options.show.delay
			);
		}

		function hideMethod(event) {
			if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; }

			// Check if new target was actually the tooltip element
			var relatedTarget = $(event.relatedTarget),
				ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],
				ontoTarget = relatedTarget[0] === this.options.show.target[0];

			// Clear timers and stop animation queue
			clearTimeout(this.timers.show);
			clearTimeout(this.timers.hide);

			// Prevent hiding if tooltip is fixed and event target is the tooltip.
			// Or if mouse positioning is enabled and cursor momentarily overlaps
			if(this !== relatedTarget[0] &&
				(this.options.position.target === 'mouse' && ontoTooltip) ||
				this.options.hide.fixed && (
					(/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
			)
			{
				/* eslint-disable no-empty */
				try {
					event.preventDefault();
					event.stopImmediatePropagation();
				} catch(e) {}
				/* eslint-enable no-empty */

				return;
			}

			// If tooltip has displayed, start hide timer
			this.timers.hide = delay.call(this,
				function() { this.toggle(FALSE, event); },
				this.options.hide.delay,
				this
			);
		}

		function inactiveMethod(event) {
			if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; }

			// Clear timer
			clearTimeout(this.timers.inactive);

			this.timers.inactive = delay.call(this,
				function(){ this.hide(event); },
				this.options.hide.inactive
			);
		}

		function repositionMethod(event) {
			if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); }
		}

		// Store mouse coordinates
		PROTOTYPE._storeMouse = function(event) {
			(this.mouse = $.event.fix(event)).type = 'mousemove';
			return this;
		};

		// Bind events
		PROTOTYPE._bind = function(targets, events, method, suffix, context) {
			if(!targets || !method || !events.length) { return; }
			var ns = '.' + this._id + (suffix ? '-'+suffix : '');
			$(targets).on(
				(events.split ? events : events.join(ns + ' ')) + ns,
				//$.proxy(method, context || this)
				method.bind(context || this)
			);
			return this;
		};
		PROTOTYPE._unbind = function(targets, suffix) {
			targets && $(targets).off('.' + this._id + (suffix ? '-'+suffix : ''));
			return this;
		};

		// Global delegation helper
		function delegate(selector, events, method) {
			$(document.body).on(selector,
				(events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE,
				function() {
					var api = QTIP.api[ $.attr(this, ATTR_ID) ];
					api && !api.disabled && method.apply(api, arguments);
				}
			);
		}
		// Event trigger
		PROTOTYPE._trigger = function(type, args, event) {
			var callback = new $.Event('tooltip'+type);
			callback.originalEvent = event && $.extend({}, event) || this.cache.event || NULL;

			this.triggering = type;
			this.tooltip.trigger(callback, [this].concat(args || []));
			this.triggering = FALSE;

			return !callback.isDefaultPrevented();
		};

		PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showCallback, hideCallback) {
			// Get tasrgets that lye within both
			var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ),
				toggleEvents = [];

			// If hide and show targets are the same...
			if(similarTargets.length) {

				// Filter identical show/hide events
				$.each(hideEvents, function(i, type) {
					var showIndex = $.inArray(type, showEvents);

					// Both events are identical, remove from both hide and show events
					// and append to toggleEvents
					showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] );
				});

				// Toggle events are special case of identical show/hide events, which happen in sequence
				if(toggleEvents.length) {
					// Bind toggle events to the similar targets
					this._bind(similarTargets, toggleEvents, function(event) {
						var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
						(state ? hideCallback : showCallback).call(this, event);
					});

					// Remove the similar targets from the regular show/hide bindings
					showTargets = showTargets.not(similarTargets);
					hideTargets = hideTargets.not(similarTargets);
				}
			}

			// Apply show/hide/toggle events
			this._bind(showTargets, showEvents, showCallback);
			this._bind(hideTargets, hideEvents, hideCallback);
		};

		PROTOTYPE._assignInitialEvents = function(event) {
			var options = this.options,
				showTarget = options.show.target,
				hideTarget = options.hide.target,
				showEvents = options.show.event ? ('' + options.show.event).trim().split(' ') : [],
				hideEvents = options.hide.event ? ('' + options.hide.event).trim().split(' ') : [];

			// Catch remove/removeqtip events on target element to destroy redundant tooltips
			this._bind(this.elements.target, ['remove', 'removeqtip'], function() {
				this.destroy(true);
			}, 'destroy');

			/*
			 * Make sure hoverIntent functions properly by using mouseleave as a hide event if
			 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
			 */
			if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
				hideEvents.push('mouseleave');
			}

			/*
			 * Also make sure initial mouse targetting works correctly by caching mousemove coords
			 * on show targets before the tooltip has rendered. Also set onTarget when triggered to
			 * keep mouse tracking working.
			 */
			this._bind(showTarget, 'mousemove', function(moveEvent) {
				this._storeMouse(moveEvent);
				this.cache.onTarget = TRUE;
			});

			// Define hoverIntent function
			function hoverIntent(hoverEvent) {
				// Only continue if tooltip isn't disabled
				if(this.disabled || this.destroyed) { return FALSE; }

				// Cache the event data
				this.cache.event = hoverEvent && $.event.fix(hoverEvent);
				this.cache.target = hoverEvent && $(hoverEvent.target);

				// Start the event sequence
				clearTimeout(this.timers.show);
				this.timers.show = delay.call(this,
					function() { this.render(typeof hoverEvent === 'object' || options.show.ready); },
					options.prerender ? 0 : options.show.delay
				);
			}

			// Filter and bind events
			this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() {
				if(!this.timers) { return FALSE; }
				clearTimeout(this.timers.show);
			});

			// Prerendering is enabled, create tooltip now
			if(options.show.ready || options.prerender) { hoverIntent.call(this, event); }
		};

		// Event assignment method
		PROTOTYPE._assignEvents = function() {
			var self = this,
				options = this.options,
				posOptions = options.position,

				tooltip = this.tooltip,
				showTarget = options.show.target,
				hideTarget = options.hide.target,
				containerTarget = posOptions.container,
				viewportTarget = posOptions.viewport,
				documentTarget = $(document),
				windowTarget = $(window),

				showEvents = options.show.event ? ('' + options.show.event).trim().split(' ') : [],
				hideEvents = options.hide.event ? ('' + options.hide.event).trim().split(' ') : [];


			// Assign passed event callbacks
			$.each(options.events, function(name, callback) {
				self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip);
			});

			// Hide tooltips when leaving current window/frame (but not select/option elements)
			if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {
				this._bind(documentTarget, ['mouseout', 'blur'], function(event) {
					if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {
						this.hide(event);
					}
				});
			}

			// Enable hide.fixed by adding appropriate class
			if(options.hide.fixed) {
				hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) );
			}

			/*
			 * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
			 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
			 */
			else if(/mouse(over|enter)/i.test(options.show.event)) {
				this._bind(hideTarget, 'mouseleave', function() {
					clearTimeout(this.timers.show);
				});
			}

			// Hide tooltip on document mousedown if unfocus events are enabled
			if(('' + options.hide.event).indexOf('unfocus') > -1) {
				this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) {
					var elem = $(event.target),
						enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0,
						isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;

					if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor &&
						!this.target.has(elem[0]).length && enabled
					) {
						this.hide(event);
					}
				});
			}

			// Check if the tooltip hides when inactive
			if('number' === typeof options.hide.inactive) {
				// Bind inactive method to show target(s) as a custom event
				this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive');

				// Define events which reset the 'inactive' event handler
				this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod);
			}

			// Filter and bind events
			this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod);

			// Mouse movement bindings
			this._bind(showTarget.add(tooltip), 'mousemove', function(event) {
				// Check if the tooltip hides when mouse is moved a certain distance
				if('number' === typeof options.hide.distance) {
					var origin = this.cache.origin || {},
						limit = this.options.hide.distance,
						abs = Math.abs;

					// Check if the movement has gone beyond the limit, and hide it if so
					if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
						this.hide(event);
					}
				}

				// Cache mousemove coords on show targets
				this._storeMouse(event);
			});

			// Mouse positioning events
			if(posOptions.target === 'mouse') {
				// If mouse adjustment is on...
				if(posOptions.adjust.mouse) {
					// Apply a mouseleave event so we don't get problems with overlapping
					if(options.hide.event) {
						// Track if we're on the target or not
						this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {
							if(!this.cache) {return FALSE; }
							this.cache.onTarget = event.type === 'mouseenter';
						});
					}

					// Update tooltip position on mousemove
					this._bind(documentTarget, 'mousemove', function(event) {
						// Update the tooltip position only if the tooltip is visible and adjustment is enabled
						if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) {
							this.reposition(event);
						}
					});
				}
			}

			// Adjust positions of the tooltip on window resize if enabled
			if(posOptions.adjust.resize || viewportTarget.length) {
				this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod );
			}

			// Adjust tooltip position on scroll of the window or viewport element if present
			if(posOptions.adjust.scroll) {
				this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod );
			}
		};

		// Un-assignment method
		PROTOTYPE._unassignEvents = function() {
			var options = this.options,
				showTargets = options.show.target,
				hideTargets = options.hide.target,
				targets = $.grep([
					this.elements.target[0],
					this.rendered && this.tooltip[0],
					options.position.container[0],
					options.position.viewport[0],
					options.position.container.closest('html')[0], // unfocus
					window,
					document
				], function(i) {
					return typeof i === 'object';
				});

			// Add show and hide targets if they're valid
			if(showTargets && showTargets.toArray) {
				targets = targets.concat(showTargets.toArray());
			}
			if(hideTargets && hideTargets.toArray) {
				targets = targets.concat(hideTargets.toArray());
			}

			// Unbind the events
			this._unbind(targets)
			._unbind(targets, 'destroy')
			._unbind(targets, 'inactive');
		};

		// Apply common event handlers using delegate (avoids excessive .bind calls!)
		$(function() {
			delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
				var state = event.type === 'mouseenter',
					tooltip = $(event.currentTarget),
					target = $(event.relatedTarget || event.target),
					options = this.options;

				// On mouseenter...
				if(state) {
					// Focus the tooltip on mouseenter (z-index stacking)
					this.focus(event);

					// Clear hide timer on tooltip hover to prevent it from closing
					tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
				}

				// On mouseleave...
				else {
					// When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set)
					if(options.position.target === 'mouse' && options.position.adjust.mouse &&
						options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) {
						this.hide(event);
					}
				}

				// Add hover class
				tooltip.toggleClass(CLASS_HOVER, state);
			});

			// Define events which reset the 'inactive' event handler
			delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
		});
		;// Initialization method
		function init(elem, id, opts) {
			var obj, posOptions, attr, config, title,

				// Setup element references
				docBody = $(document.body),

				// Use document body instead of document element if needed
				newTarget = elem[0] === document ? docBody : elem,

				// Grab metadata from element if plugin is present
				metadata = elem.metadata ? elem.metadata(opts.metadata) : NULL,

				// If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
				metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,

				// Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
				html5 = elem.data(opts.metadata.name || 'qtipopts');

			// If we don't get an object returned attempt to parse it manualyl without parseJSON
			/* eslint-disable no-empty */
			try { html5 = typeof html5 === 'string' ? JSON.parse(html5) : html5; }
			catch(e) {}
			/* eslint-enable no-empty */

			// Merge in and sanitize metadata
			config = $.extend(TRUE, {}, QTIP.defaults, opts,
				typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
				sanitizeOptions(metadata5 || metadata));

			// Re-grab our positioning options now we've merged our metadata and set id to passed value
			posOptions = config.position;
			config.id = id;

			// Setup missing content if none is detected
			if('boolean' === typeof config.content.text) {
				attr = elem.attr(config.content.attr);

				// Grab from supplied attribute if available
				if(config.content.attr !== FALSE && attr) { config.content.text = attr; }

				// No valid content was found, abort render
				else { return FALSE; }
			}

			// Setup target options
			if(!posOptions.container.length) { posOptions.container = docBody; }
			if(posOptions.target === FALSE) { posOptions.target = newTarget; }
			if(config.show.target === FALSE) { config.show.target = newTarget; }
			if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
			if(config.hide.target === FALSE) { config.hide.target = newTarget; }
			if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }

			// Ensure we only use a single container
			posOptions.container = posOptions.container.eq(0);

			// Convert position corner values into x and y strings
			posOptions.at = new CORNER(posOptions.at, TRUE);
			posOptions.my = new CORNER(posOptions.my);

			// Destroy previous tooltip if overwrite is enabled, or skip element if not
			if(elem.data(NAMESPACE)) {
				if(config.overwrite) {
					elem.qtip('destroy', true);
				}
				else if(config.overwrite === FALSE) {
					return FALSE;
				}
			}

			// Add has-qtip attribute
			elem.attr(ATTR_HAS, id);

			// Remove title attribute and store it if present
			if(config.suppress && (title = elem.attr('title'))) {
				// Final attr call fixes event delegatiom and IE default tooltip showing problem
				elem.removeAttr('title').attr(oldtitle, title).attr('title', '');
			}

			// Initialize the tooltip and add API reference
			obj = new QTip(elem, config, id, !!attr);
			elem.data(NAMESPACE, obj);

			return obj;
		}

		// jQuery $.fn extension method
		QTIP = $.fn.qtip = function(options, notation, newValue)
		{
			var command = ('' + options).toLowerCase(), // Parse command
				returned = NULL,
				args = $.makeArray(arguments).slice(1),
				event = args[args.length - 1],
				opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;

			// Check for API request
			if(!arguments.length && opts || command === 'api') {
				return opts;
			}

			// Execute API command if present
			else if('string' === typeof options) {
				this.each(function() {
					var api = $.data(this, NAMESPACE);
					if(!api) { return TRUE; }

					// Cache the event if possible
					if(event && event.timeStamp) { api.cache.event = event; }

					// Check for specific API commands
					if(notation && (command === 'option' || command === 'options')) {
						if(newValue !== undefined || $.isPlainObject(notation)) {
							api.set(notation, newValue);
						}
						else {
							returned = api.get(notation);
							return FALSE;
						}
					}

					// Execute API command
					else if(api[command]) {
						api[command].apply(api, args);
					}
				});

				return returned !== NULL ? returned : this;
			}

			// No API commands. validate provided options and setup qTips
			else if('object' === typeof options || !arguments.length) {
				// Sanitize options first
				opts = sanitizeOptions($.extend(TRUE, {}, options));

				return this.each(function(i) {
					var api, id;

					// Find next available ID, or use custom ID if provided
					id = Array.isArray(opts.id) ? opts.id[i] : opts.id;
					id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;

					// Initialize the qTip and re-grab newly sanitized options
					api = init($(this), id, opts);
					if(api === FALSE) { return TRUE; }
					else { QTIP.api[id] = api; }

					// Initialize plugins
					$.each(PLUGINS, function() {
						if(this.initialize === 'initialize') { this(api); }
					});

					// Assign initial pre-render events
					api._assignInitialEvents(event);
				});
			}
		};

		// Expose class
		$.qtip = QTip;

		// Populated in render method
		QTIP.api = {};
		;$.each({
			/* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
			attr: function(attr, val) {
				if ('boolean' === typeof val) {
					val = val.toString();
				}

				if(this.length) {
					var self = this[0],
						title = 'title',
						api = $.data(self, 'qtip');

					if(attr === title && api && api.options && 'object' === typeof api && 'object' === typeof api.options && api.options.suppress) {
						if(arguments.length < 2) {
							return $.attr(self, oldtitle);
						}

						// If qTip is rendered and title was originally used as content, update it
						if(api && api.options.content.attr === title && api.cache.attr) {
							api.set('content.text', val);
						}

						// Use the regular attr method to set, then cache the result
						return this.attr(oldtitle, val);
					}
				}

				//let argVal = arguments[1];
				//if ('boolean' === typeof argVal) {
				//	arguments[1] = argVal.toString();
				//}

				return $.fn['attr'+replaceSuffix].apply(this, arguments);
			},

			/* Allow clone to correctly retrieve cached title attributes */
			clone: function(keepData) {
				// Clone our element using the real clone method
				var elems = $.fn['clone'+replaceSuffix].apply(this, arguments);

				// Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
				if(!keepData) {
					elems.filter('['+oldtitle+']').attr('title', function() {
						return $.attr(this, oldtitle);
					})
					.removeAttr(oldtitle);
				}

				return elems;
			}
		}, function(name, func) {
			if(!func || $.fn[name+replaceSuffix]) { return TRUE; }

			var old = $.fn[name+replaceSuffix] = $.fn[name];
			$.fn[name] = function() {
				return func.apply(this, arguments) || old.apply(this, arguments);
			};
		});

		/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
		 * This snippet is taken directly from jQuery UI source code found here:
		 *     http://code.jquery.com/ui/jquery-ui-git.js
		 */
		if(!$.ui) {
			$['cleanData'+replaceSuffix] = $.cleanData;
			$.cleanData = function( elems ) {
				for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {
					if(elem.attr(ATTR_HAS)) {
						/* eslint-disable no-empty */
						try { elem.triggerHandler('removeqtip'); }
						catch( e ) {}
						/* eslint-enable no-empty */
					}
				}
				$['cleanData'+replaceSuffix].apply(this, arguments);
			};
		}
		;// qTip version
		QTIP.version = '3.0.3';

		// Base ID for all qTips
		QTIP.nextid = 0;

		// Inactive events array
		QTIP.inactiveEvents = INACTIVE_EVENTS;

		// Base z-index for all qTips
		QTIP.zindex = 15000;

		// Define configuration defaults
		QTIP.defaults = {
			prerender: FALSE,
			id: FALSE,
			overwrite: TRUE,
			suppress: TRUE,
			content: {
				text: TRUE,
				attr: 'title',
				title: FALSE,
				button: FALSE
			},
			position: {
				my: 'top left',
				at: 'bottom right',
				target: FALSE,
				container: FALSE,
				viewport: FALSE,
				adjust: {
					x: 0, y: 0,
					mouse: TRUE,
					scroll: TRUE,
					resize: TRUE,
					method: 'flipinvert flipinvert'
				},
				effect: function(api, pos) {
					$(this).animate(pos, {
						duration: 200,
						queue: FALSE
					});
				}
			},
			show: {
				target: FALSE,
				event: 'mouseenter',
				effect: TRUE,
				delay: 90,
				solo: FALSE,
				ready: FALSE,
				autofocus: FALSE
			},
			hide: {
				target: FALSE,
				event: 'mouseleave',
				effect: TRUE,
				delay: 0,
				fixed: FALSE,
				inactive: FALSE,
				leave: 'window',
				distance: FALSE
			},
			style: {
				classes: '',
				widget: FALSE,
				width: FALSE,
				height: FALSE,
				def: TRUE
			},
			events: {
				render: NULL,
				move: NULL,
				show: NULL,
				hide: NULL,
				toggle: NULL,
				visible: NULL,
				hidden: NULL,
				focus: NULL,
				blur: NULL
			}
		};
		;var TIP,
			createVML,
			SCALE,
			PIXEL_RATIO,
			BACKING_STORE_RATIO,

			// Common CSS strings
			MARGIN = 'margin',
			BORDER = 'border',
			COLOR = 'color',
			BG_COLOR = 'background-color',
			TRANSPARENT = 'transparent',
			IMPORTANT = ' !important',

			// Check if the browser supports <canvas/> elements
			HASCANVAS = !!document.createElement('canvas').getContext,

			// Invalid colour values used in parseColours()
			INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i;

		// Camel-case method, taken from jQuery source
		// http://code.jquery.com/jquery-1.8.0.js
		function camel(s) { return s.charAt(0).toUpperCase() + s.slice(1); }

		/*
		 * Modified from Modernizr's testPropsAll()
		 * http://modernizr.com/downloads/modernizr-latest.js
		 */
		var cssProps = {}, cssPrefixes = ['Webkit', 'O', 'Moz', 'ms'];
		function vendorCss(elem, prop) {
			var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
				props = (prop + ' ' + cssPrefixes.join(ucProp + ' ') + ucProp).split(' '),
				cur, val, i = 0;

			// If the property has already been mapped...
			if(cssProps[prop]) { return elem.css(cssProps[prop]); }

			while(cur = props[i++]) {
				if((val = elem.css(cur)) !== undefined) {
					cssProps[prop] = cur;
					return val;
				}
			}
		}

		// Parse a given elements CSS property into an int
		function intCss(elem, prop) {
			return Math.ceil(parseFloat(vendorCss(elem, prop)));
		}


		// VML creation (for IE only)
		if(!HASCANVAS) {
			createVML = function(tag, props, style) {
				return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+
					' style="behavior: url(#default#VML); '+(style||'')+ '" />';
			};
		}

		// Canvas only definitions
		else {
			PIXEL_RATIO = window.devicePixelRatio || 1;
			BACKING_STORE_RATIO = (function() {
				var context = document.createElement('canvas').getContext('2d');
				return context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio ||
					context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || 1;
			})();
			SCALE = PIXEL_RATIO / BACKING_STORE_RATIO;
		}


		function Tip(qtip, options) {
			this._ns = 'tip';
			this.options = options;
			this.offset = options.offset;
			this.size = [ options.width, options.height ];

			// Initialize
			this.qtip = qtip;
			this.init(qtip);
		}

		$.extend(Tip.prototype, {
			init: function(qtip) {
				var context, tip;

				// Create tip element and prepend to the tooltip
				tip = this.element = qtip.elements.tip = $('<div />', { 'class': NAMESPACE+'-tip' }).prependTo(qtip.tooltip);

				// Create tip drawing element(s)
				if(HASCANVAS) {
					// save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
					context = $('<canvas />').appendTo(this.element)[0].getContext('2d');

					// Setup constant parameters
					context.lineJoin = 'miter';
					context.miterLimit = 100000;
					context.save();
				}
				else {
					context = createVML('shape', 'coordorigin="0,0"', 'position:absolute;');
					this.element.html(context + context);

					// Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML
					qtip._bind( $('*', tip).add(tip), ['click', 'mousedown'], function(event) { event.stopPropagation(); }, this._ns);
				}

				// Bind update events
				qtip._bind(qtip.tooltip, 'tooltipmove', this.reposition, this._ns, this);

				// Create it
				this.create();
			},

			_swapDimensions: function() {
				this.size[0] = this.options.height;
				this.size[1] = this.options.width;
			},
			_resetDimensions: function() {
				this.size[0] = this.options.width;
				this.size[1] = this.options.height;
			},

			_useTitle: function(corner) {
				var titlebar = this.qtip.elements.titlebar;
				return titlebar && (
					corner.y === TOP || corner.y === CENTER && this.element.position().top + this.size[1] / 2 + this.options.offset < titlebar.outerHeight(TRUE)
				);
			},

			_parseCorner: function(corner) {
				var my = this.qtip.options.position.my;

				// Detect corner and mimic properties
				if(corner === FALSE || my === FALSE) {
					corner = FALSE;
				}
				else if(corner === TRUE) {
					corner = new CORNER( my.string() );
				}
				else if(!corner.string) {
					corner = new CORNER(corner);
					corner.fixed = TRUE;
				}

				return corner;
			},

			_parseWidth: function(corner, side, use) {
				var elements = this.qtip.elements,
					prop = BORDER + camel(side) + 'Width';

				return (use ? intCss(use, prop) :
						intCss(elements.content, prop) ||
						intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
						intCss(elements.tooltip, prop)
				) || 0;
			},

			_parseRadius: function(corner) {
				var elements = this.qtip.elements,
					prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius';

				return BROWSER.ie < 9 ? 0 :
					intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
					intCss(elements.tooltip, prop) || 0;
			},

			_invalidColour: function(elem, prop, compare) {
				var val = elem.css(prop);
				return !val || compare && val === elem.css(compare) || INVALID.test(val) ? FALSE : val;
			},

			_parseColours: function(corner) {
				var elements = this.qtip.elements,
					tip = this.element.css('cssText', ''),
					borderSide = BORDER + camel(corner[ corner.precedance ]) + camel(COLOR),
					colorElem = this._useTitle(corner) && elements.titlebar || elements.content,
					css = this._invalidColour, color = [];

				// Attempt to detect the background colour from various elements, left-to-right precedance
				color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) ||
					css(elements.tooltip, BG_COLOR) || tip.css(BG_COLOR);

				// Attempt to detect the correct border side colour from various elements, left-to-right precedance
				color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) ||
					css(elements.content, borderSide, COLOR) || css(elements.tooltip, borderSide, COLOR) || elements.tooltip.css(borderSide);

				// Reset background and border colours
				$('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';');

				return color;
			},

			_calculateSize: function(corner) {
				var y = corner.precedance === Y,
					width = this.options.width,
					height = this.options.height,
					isCenter = corner.abbrev() === 'c',
					base = (y ? width: height) * (isCenter ? 0.5 : 1),
					pow = Math.pow,
					round = Math.round,
					bigHyp, ratio, result,

					smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
					hyp = [
						this.border / base * smallHyp,
						this.border / height * smallHyp
					];

				hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(this.border, 2) );
				hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(this.border, 2) );

				bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
				ratio = bigHyp / smallHyp;

				result = [ round(ratio * width), round(ratio * height) ];
				return y ? result : result.reverse();
			},

			// Tip coordinates calculator
			_calculateTip: function(corner, size, scale) {
				scale = scale || 1;
				size = size || this.size;

				var width = size[0] * scale,
					height = size[1] * scale,
					width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),

					// Define tip coordinates in terms of height and width values
					tips = {
						br:	[0,0,		width,height,	width,0],
						bl:	[0,0,		width,0,		0,height],
						tr:	[0,height,	width,0,		width,height],
						tl:	[0,0,		0,height,		width,height],
						tc:	[0,height,	width2,0,		width,height],
						bc:	[0,0,		width,0,		width2,height],
						rc:	[0,0,		width,height2,	0,height],
						lc:	[width,0,	width,height,	0,height2]
					};

				// Set common side shapes
				tips.lt = tips.br; tips.rt = tips.bl;
				tips.lb = tips.tr; tips.rb = tips.tl;

				return tips[ corner.abbrev() ];
			},

			// Tip coordinates drawer (canvas)
			_drawCoords: function(context, coords) {
				context.beginPath();
				context.moveTo(coords[0], coords[1]);
				context.lineTo(coords[2], coords[3]);
				context.lineTo(coords[4], coords[5]);
				context.closePath();
			},

			create: function() {
				// Determine tip corner
				var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner);

				// If we have a tip corner...
				this.enabled = !!this.corner && this.corner.abbrev() !== 'c';
				if(this.enabled) {
					// Cache it
					this.qtip.cache.corner = c.clone();

					// Create it
					this.update();
				}

				// Toggle tip element
				this.element.toggle(this.enabled);

				return this.corner;
			},

			update: function(corner, position) {
				if(!this.enabled) { return this; }

				var elements = this.qtip.elements,
					tip = this.element,
					inner = tip.children(),
					options = this.options,
					curSize = this.size,
					mimic = options.mimic,
					round = Math.round,
					color, precedance, context,
					coords, bigCoords, translate, newSize, border;

				// Re-determine tip if not already set
				if(!corner) { corner = this.qtip.cache.corner || this.corner; }

				// Use corner property if we detect an invalid mimic value
				if(mimic === FALSE) { mimic = corner; }

				// Otherwise inherit mimic properties from the corner object as necessary
				else {
					mimic = new CORNER(mimic);
					mimic.precedance = corner.precedance;

					if(mimic.x === 'inherit') { mimic.x = corner.x; }
					else if(mimic.y === 'inherit') { mimic.y = corner.y; }
					else if(mimic.x === mimic.y) {
						mimic[ corner.precedance ] = corner[ corner.precedance ];
					}
				}
				precedance = mimic.precedance;

				// Ensure the tip width.height are relative to the tip position
				if(corner.precedance === X) { this._swapDimensions(); }
				else { this._resetDimensions(); }

				// Update our colours
				color = this.color = this._parseColours(corner);

				// Detect border width, taking into account colours
				if(color[1] !== TRANSPARENT) {
					// Grab border width
					border = this.border = this._parseWidth(corner, corner[corner.precedance]);

					// If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips)
					if(options.border && border < 1 && !INVALID.test(color[1])) { color[0] = color[1]; }

					// Set border width (use detected border width if options.border is true)
					this.border = border = options.border !== TRUE ? options.border : border;
				}

				// Border colour was invalid, set border to zero
				else { this.border = border = 0; }

				// Determine tip size
				newSize = this.size = this._calculateSize(corner);
				tip.css({
					width: newSize[0],
					height: newSize[1],
					lineHeight: newSize[1]+'px'
				});

				// Calculate tip translation
				if(corner.precedance === Y) {
					translate = [
						round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - curSize[0] - border : (newSize[0] - curSize[0]) / 2),
						round(mimic.y === TOP ? newSize[1] - curSize[1] : 0)
					];
				}
				else {
					translate = [
						round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0),
						round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - curSize[1] - border : (newSize[1] - curSize[1]) / 2)
					];
				}

				// Canvas drawing implementation
				if(HASCANVAS) {
					// Grab canvas context and clear/save it
					context = inner[0].getContext('2d');
					context.restore(); context.save();
					context.clearRect(0,0,6000,6000);

					// Calculate coordinates
					coords = this._calculateTip(mimic, curSize, SCALE);
					bigCoords = this._calculateTip(mimic, this.size, SCALE);

					// Set the canvas size using calculated size
					inner.attr(WIDTH, newSize[0] * SCALE).attr(HEIGHT, newSize[1] * SCALE);
					inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]);

					// Draw the outer-stroke tip
					this._drawCoords(context, bigCoords);
					context.fillStyle = color[1];
					context.fill();

					// Draw the actual tip
					context.translate(translate[0] * SCALE, translate[1] * SCALE);
					this._drawCoords(context, coords);
					context.fillStyle = color[0];
					context.fill();
				}

				// VML (IE Proprietary implementation)
				else {
					// Calculate coordinates
					coords = this._calculateTip(mimic);

					// Setup coordinates string
					coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] +
						',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe';

					// Setup VML-specific offset for pixel-perfection
					translate[2] = border && /^(r|b)/i.test(corner.string()) ?
						BROWSER.ie === 8 ? 2 : 1 : 0;

					// Set initial CSS
					inner.css({
						coordsize: newSize[0]+border + ' ' + newSize[1]+border,
						antialias: ''+(mimic.string().indexOf(CENTER) > -1),
						left: translate[0] - translate[2] * Number(precedance === X),
						top: translate[1] - translate[2] * Number(precedance === Y),
						width: newSize[0] + border,
						height: newSize[1] + border
					})
					.each(function(i) {
						var $this = $(this);

						// Set shape specific attributes
						$this[ $this.prop ? 'prop' : 'attr' ]({
							coordsize: newSize[0]+border + ' ' + newSize[1]+border,
							path: coords,
							fillcolor: color[0],
							filled: !!i,
							stroked: !i
						})
						.toggle(!!(border || i));

						// Check if border is enabled and add stroke element
						!i && $this.html( createVML(
							'stroke', 'weight="'+border*2+'px" color="'+color[1]+'" miterlimit="1000" joinstyle="miter"'
						) );
					});
				}

				// Opera bug #357 - Incorrect tip position
				// https://github.com/Craga89/qTip2/issues/367
				window.opera && setTimeout(function() {
					elements.tip.css({
						display: 'inline-block',
						visibility: 'visible'
					});
				}, 1);

				// Position if needed
				if(position !== FALSE) { this.calculate(corner, newSize); }
			},

			calculate: function(corner, size) {
				if(!this.enabled) { return FALSE; }

				var self = this,
					elements = this.qtip.elements,
					tip = this.element,
					userOffset = this.options.offset,
					position = {},
					precedance, corners;

				// Inherit corner if not provided
				corner = corner || this.corner;
				precedance = corner.precedance;

				// Determine which tip dimension to use for adjustment
				size = size || this._calculateSize(corner);

				// Setup corners and offset array
				corners = [ corner.x, corner.y ];
				if(precedance === X) { corners.reverse(); }

				// Calculate tip position
				$.each(corners, function(i, side) {
					var b, bc, br;

					if(side === CENTER) {
						b = precedance === Y ? LEFT : TOP;
						position[ b ] = '50%';
						position[MARGIN+'-' + b] = -Math.round(size[ precedance === Y ? 0 : 1 ] / 2) + userOffset;
					}
					else {
						b = self._parseWidth(corner, side, elements.tooltip);
						bc = self._parseWidth(corner, side, elements.content);
						br = self._parseRadius(corner);

						position[ side ] = Math.max(-self.border, i ? bc : userOffset + (br > b ? br : -b));
					}
				});

				// Adjust for tip size
				position[ corner[precedance] ] -= size[ precedance === X ? 0 : 1 ];

				// Set and return new position
				tip.css({ margin: '', top: '', bottom: '', left: '', right: '' }).css(position);
				return position;
			},

			reposition: function(event, api, pos) {
				if(!this.enabled) { return; }

				var cache = api.cache,
					newCorner = this.corner.clone(),
					adjust = pos.adjusted,
					method = api.options.position.adjust.method.split(' '),
					horizontal = method[0],
					vertical = method[1] || method[0],
					shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
					offset, css = {}, props;

				function shiftflip(direction, precedance, popposite, side, opposite) {
					// Horizontal - Shift or flip method
					if(direction === SHIFT && newCorner.precedance === precedance && adjust[side] && newCorner[popposite] !== CENTER) {
						newCorner.precedance = newCorner.precedance === X ? Y : X;
					}
					else if(direction !== SHIFT && adjust[side]){
						newCorner[precedance] = newCorner[precedance] === CENTER ?
							adjust[side] > 0 ? side : opposite :
							newCorner[precedance] === side ? opposite : side;
					}
				}

				function shiftonly(xy, side, opposite) {
					if(newCorner[xy] === CENTER) {
						css[MARGIN+'-'+side] = shift[xy] = offset[MARGIN+'-'+side] - adjust[side];
					}
					else {
						props = offset[opposite] !== undefined ?
							[ adjust[side], -offset[side] ] : [ -adjust[side], offset[side] ];

						if( (shift[xy] = Math.max(props[0], props[1])) > props[0] ) {
							pos[side] -= adjust[side];
							shift[side] = FALSE;
						}

						css[ offset[opposite] !== undefined ? opposite : side ] = shift[xy];
					}
				}

				// If our tip position isn't fixed e.g. doesn't adjust with viewport...
				if(this.corner.fixed !== TRUE) {
					// Perform shift/flip adjustments
					shiftflip(horizontal, X, Y, LEFT, RIGHT);
					shiftflip(vertical, Y, X, TOP, BOTTOM);

					// Update and redraw the tip if needed (check cached details of last drawn tip)
					if(newCorner.string() !== cache.corner.string() || cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left) {
						this.update(newCorner, FALSE);
					}
				}

				// Setup tip offset properties
				offset = this.calculate(newCorner);

				// Readjust offset object to make it left/top
				if(offset.right !== undefined) { offset.left = -offset.right; }
				if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
				offset.user = this.offset;

				// Perform shift adjustments
				shift.left = horizontal === SHIFT && !!adjust.left;
				if(shift.left) {
					shiftonly(X, LEFT, RIGHT);
				}
				shift.top = vertical === SHIFT && !!adjust.top;
				if(shift.top) {
					shiftonly(Y, TOP, BOTTOM);
				}

				/*
				* If the tip is adjusted in both dimensions, or in a
				* direction that would cause it to be anywhere but the
				* outer border, hide it!
				*/
				this.element.css(css).toggle(
					!(shift.x && shift.y || newCorner.x === CENTER && shift.y || newCorner.y === CENTER && shift.x)
				);

				// Adjust position to accomodate tip dimensions
				pos.left -= offset.left.charAt ? offset.user :
					horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left + this.border : 0;
				pos.top -= offset.top.charAt ? offset.user :
					vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top + this.border : 0;

				// Cache details
				cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top;
				cache.corner = newCorner.clone();
			},

			destroy: function() {
				// Unbind events
				this.qtip._unbind(this.qtip.tooltip, this._ns);

				// Remove the tip element(s)
				if(this.qtip.elements.tip) {
					this.qtip.elements.tip.find('*')
					.remove().end().remove();
				}
			}
		});

		TIP = PLUGINS.tip = function(api) {
			return new Tip(api, api.options.style.tip);
		};

		// Initialize tip on render
		TIP.initialize = 'render';

		// Setup plugin sanitization options
		TIP.sanitize = function(options) {
			if(options.style && 'tip' in options.style) {
				var opts = options.style.tip;
				if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; }
				if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }
			}
		};

		// Add new option checks for the plugin
		CHECKS.tip = {
			'^position.my|style.tip.(corner|mimic|border)$': function() {
				// Make sure a tip can be drawn
				this.create();

				// Reposition the tooltip
				this.qtip.reposition();
			},
			'^style.tip.(height|width)$': function(obj) {
				// Re-set dimensions and redraw the tip
				this.size = [ obj.width, obj.height ];
				this.update();

				// Reposition the tooltip
				this.qtip.reposition();
			},
			'^content.title|style.(classes|widget)$': function() {
				this.update();
			}
		};

		// Extend original qTip defaults
		$.extend(TRUE, QTIP.defaults, {
			style: {
				tip: {
					corner: TRUE,
					mimic: FALSE,
					width: 6,
					height: 6,
					border: TRUE,
					offset: 0
				}
			}
		});
		;var MODAL, OVERLAY,
			MODALCLASS = 'qtip-modal',
			MODALSELECTOR = '.'+MODALCLASS;

		OVERLAY = function()
		{
			var self = this,
				focusableElems = {},
				current,
				prevState,
				elem;

			// Modified code from jQuery UI 1.10.0 source
			// http://code.jquery.com/ui/1.10.0/jquery-ui.js
			function focusable(element) {
				// Use the defined focusable checker when possible
				if($.expr[':'].focusable) { return $.expr[':'].focusable; }

				var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')),
					nodeName = element.nodeName && element.nodeName.toLowerCase(),
					map, mapName, img;

				if('area' === nodeName) {
					map = element.parentNode;
					mapName = map.name;
					if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
						return false;
					}
					img = $('img[usemap=#' + mapName + ']')[0];
					return !!img && img.is(':visible');
				}

				return /input|select|textarea|button|object/.test( nodeName ) ?
					!element.disabled :
					'a' === nodeName ?
						element.href || isTabIndexNotNaN :
						isTabIndexNotNaN
					;
			}

			// Focus inputs using cached focusable elements (see update())
			function focusInputs(blurElems) {
				// Blurring body element in IE causes window.open windows to unfocus!
				if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }

				// Focus the inputs
				else { focusableElems.first().focus(); }
			}

			// Steal focus from elements outside tooltip
			function stealFocus(event) {
				if(!elem.is(':visible')) { return; }

				var target = $(event.target),
					tooltip = current.tooltip,
					container = target.closest(SELECTOR),
					targetOnTop;

				// Determine if input container target is above this
				targetOnTop = container.length < 1 ? FALSE :
					parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10);

				// If we're showing a modal, but focus has landed on an input below
				// this modal, divert focus to the first visible input in this modal
				// or if we can't find one... the tooltip itself
				if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) {
					focusInputs(target);
				}
			}

			$.extend(self, {
				init: function() {
					// Create document overlay
					elem = self.elem = $('<div />', {
						id: 'qtip-overlay',
						html: '<div></div>',
					})
					elem.on(
						'mousedown',
						function() {
							return false;
						}
					)
					.hide();

					// Make sure we can't focus anything outside the tooltip
					$(document.body).on('focusin'+MODALSELECTOR, stealFocus);

					// Apply keyboard "Escape key" close handler
					$(document).on('keydown'+MODALSELECTOR, function(event) {
						if(current && current.options.show.modal.escape && event.keyCode === 27) {
							current.hide(event);
						}
					});

					// Apply click handler for blur option
					elem.on('click'+MODALSELECTOR, function(event) {
						if(current && current.options.show.modal.blur) {
							current.hide(event);
						}
					});

					return self;
				},

				update: function(api) {
					// Update current API reference
					current = api;

					// Update focusable elements if enabled
					if(api.options.show.modal.stealfocus !== FALSE) {
						focusableElems = api.tooltip.find('*').filter(function() {
							return focusable(this);
						});
					}
					else { focusableElems = []; }
				},

				toggle: function(api, state, duration) {
					var tooltip = api.tooltip,
						options = api.options.show.modal,
						effect = options.effect,
						type = state ? 'show': 'hide',
						visible = elem.is(':visible'),
						visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip);

					// Set active tooltip API reference
					self.update(api);

					// If the modal can steal the focus...
					// Blur the current item and focus anything in the modal we an
					if(state && options.stealfocus !== FALSE) {
						focusInputs( $(':focus') );
					}

					// Toggle backdrop cursor style on show
					elem.toggleClass('blurs', options.blur);

					// Append to body on show
					if(state) {
						elem.appendTo(document.body);
					}

					// Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
					if(elem.is(':animated') && visible === state && prevState !== FALSE || !state && visibleModals.length) {
						return self;
					}

					// Stop all animations
					elem.stop(TRUE, FALSE);

					// Use custom function if provided
					if(typeof effect === 'function') {
						effect.call(elem, state);
					}

					// If no effect type is supplied, use a simple toggle
					else if(effect === FALSE) {
						elem[ type ]();
					}

					// Use basic fade function
					else {
						elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
							if(!state) { elem.hide(); }
						});
					}

					// Reset position and detach from body on hide
					if(!state) {
						elem.queue(function(next) {
							elem.css({ left: '', top: '' });
							if(!$(MODALSELECTOR).length) { elem.detach(); }
							next();
						});
					}

					// Cache the state
					prevState = state;

					// If the tooltip is destroyed, set reference to null
					if(current.destroyed) { current = NULL; }

					return self;
				}
			});

			self.init();
		};
		OVERLAY = new OVERLAY();

		function Modal(api, options) {
			this.options = options;
			this._ns = '-modal';

			this.qtip = api;
			this.init(api);
		}

		$.extend(Modal.prototype, {
			init: function(qtip) {
				var tooltip = qtip.tooltip;

				// If modal is disabled... return
				if(!this.options.on) { return this; }

				// Set overlay reference
				qtip.elements.overlay = OVERLAY.elem;

				// Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index
				tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length);

				// Apply our show/hide/focus modal events
				qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) {
					var oEvent = event.originalEvent;

					// Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
					if(event.target === tooltip[0]) {
						if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) {
							/* eslint-disable no-empty */
							try { event.preventDefault(); }
							catch(e) {}
							/* eslint-enable no-empty */
						}
						else if(!oEvent || oEvent && oEvent.type !== 'tooltipsolo') {
							this.toggle(event, event.type === 'tooltipshow', duration);
						}
					}
				}, this._ns, this);

				// Adjust modal z-index on tooltip focus
				qtip._bind(tooltip, 'tooltipfocus', function(event, api) {
					// If focus was cancelled before it reached us, don't do anything
					if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }

					var qtips = $(MODALSELECTOR),

						// Keep the modal's lower than other, regular qtips
						newIndex = QTIP.modal_zindex + qtips.length,
						curIndex = parseInt(tooltip[0].style.zIndex, 10);

					// Set overlay z-index
					OVERLAY.elem[0].style.zIndex = newIndex - 1;

					// Reduce modal z-index's and keep them properly ordered
					qtips.each(function() {
						if(this.style.zIndex > curIndex) {
							this.style.zIndex -= 1;
						}
					});

					// Fire blur event for focused tooltip
					qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent);

					// Set the new z-index
					tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;

					// Set current
					OVERLAY.update(api);

					// Prevent default handling
					/* eslint-disable no-empty */
					try { event.preventDefault(); }
					catch(e) {}
					/* eslint-enable no-empty */
				}, this._ns, this);

				// Focus any other visible modals when this one hides
				qtip._bind(tooltip, 'tooltiphide', function(event) {
					if(event.target === tooltip[0]) {
						$(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event);
					}
				}, this._ns, this);
			},

			toggle: function(event, state, duration) {
				// Make sure default event hasn't been prevented
				if(event && event.isDefaultPrevented()) { return this; }

				// Toggle it
				OVERLAY.toggle(this.qtip, !!state, duration);
			},

			destroy: function() {
				// Remove modal class
				this.qtip.tooltip.removeClass(MODALCLASS);

				// Remove bound events
				this.qtip._unbind(this.qtip.tooltip, this._ns);

				// Delete element reference
				OVERLAY.toggle(this.qtip, FALSE);
				delete this.qtip.elements.overlay;
			}
		});


		MODAL = PLUGINS.modal = function(api) {
			return new Modal(api, api.options.show.modal);
		};

		// Setup sanitiztion rules
		MODAL.sanitize = function(opts) {
			if(opts.show) {
				if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
				else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
			}
		};

		// Base z-index for all modal tooltips (use qTip core z-index as a base)
		/* eslint-disable camelcase */
		QTIP.modal_zindex = QTIP.zindex - 200;
		/* eslint-enable camelcase */

		// Plugin needs to be initialized on render
		MODAL.initialize = 'render';

		// Setup option set checks
		CHECKS.modal = {
			'^show.modal.(on|blur)$': function() {
				// Initialise
				this.destroy();
				this.init();

				// Show the modal if not visible already and tooltip is visible
				this.qtip.elems.overlay.toggle(
					this.qtip.tooltip[0].offsetWidth > 0
				);
			}
		};

		// Extend original api defaults
		$.extend(TRUE, QTIP.defaults, {
			show: {
				modal: {
					on: FALSE,
					effect: TRUE,
					blur: TRUE,
					stealfocus: TRUE,
					escape: TRUE
				}
			}
		});
		;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
		{
			var target = posOptions.target,
				tooltip = api.elements.tooltip,
				my = posOptions.my,
				at = posOptions.at,
				adjust = posOptions.adjust,
				method = adjust.method.split(' '),
				methodX = method[0],
				methodY = method[1] || method[0],
				viewport = posOptions.viewport,
				container = posOptions.container,
				adjusted = { left: 0, top: 0 },
				fixed, newMy, containerOffset, containerStatic,
				viewportWidth, viewportHeight, viewportScroll, viewportOffset;

			// If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return
			if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
				return adjusted;
			}

			// Cach container details
			containerOffset = container.offset() || adjusted;
			containerStatic = container.css('position') === 'static';

			// Cache our viewport details
			fixed = tooltip.css('position') === 'fixed';
			viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE);
			viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE);
			viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() };
			viewportOffset = viewport.offset() || adjusted;

			// Generic calculation method
			function calculate(side, otherSide, type, adjustment, side1, side2, lengthName, targetLength, elemLength) {
				var initialPos = position[side1],
					mySide = my[side],
					atSide = at[side],
					isShift = type === SHIFT,
					myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
					atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
					sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]),
					overflow1 = sideOffset - initialPos,
					overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset,
					offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);

				// shift
				if(isShift) {
					offset = (mySide === side1 ? 1 : -1) * myLength;

					// Adjust position but keep it within viewport dimensions
					position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
					position[side1] = Math.max(
						-containerOffset[side1] + viewportOffset[side1],
						initialPos - offset,
						Math.min(
							Math.max(
								-containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight),
								initialPos + offset
							),
							position[side1],

							// Make sure we don't adjust complete off the element when using 'center'
							mySide === 'center' ? initialPos - myLength : 1E9
						)
					);

				}

				// flip/flipinvert
				else {
					// Update adjustment amount depending on if using flipinvert or flip
					adjustment *= type === FLIPINVERT ? 2 : 0;

					// Check for overflow on the left/top
					if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
						position[side1] -= offset + adjustment;
						newMy.invert(side, side1);
					}

					// Check for overflow on the bottom/right
					else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0)  ) {
						position[side1] -= (mySide === CENTER ? -offset : offset) + adjustment;
						newMy.invert(side, side2);
					}

					// Make sure we haven't made things worse with the adjustment and reset if so
					if(position[side1] < viewportScroll[side1] && -position[side1] > overflow2) {
						position[side1] = initialPos; newMy = my.clone();
					}
				}

				return position[side1] - initialPos;
			}

			// Set newMy if using flip or flipinvert methods
			if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }

			// Adjust position based onviewport and adjustment options
			adjusted = {
				left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
				top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0,
				my: newMy
			};

			return adjusted;
		};
		;PLUGINS.polys = {
			// POLY area coordinate calculator
			//	Special thanks to Ed Cradock for helping out with this.
			//	Uses a binary search algorithm to find suitable coordinates.
			polygon: function(baseCoords, corner) {
				var result = {
						width: 0, height: 0,
						position: {
							top: 1e10, right: 0,
							bottom: 0, left: 1e10
						},
						adjustable: FALSE
					},
					i = 0, next,
					coords = [],
					compareX = 1, compareY = 1,
					realX = 0, realY = 0,
					newWidth, newHeight;

				// First pass, sanitize coords and determine outer edges
				i = baseCoords.length;
				while(i--) {
					next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];

					if(next[0] > result.position.right){ result.position.right = next[0]; }
					if(next[0] < result.position.left){ result.position.left = next[0]; }
					if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
					if(next[1] < result.position.top){ result.position.top = next[1]; }

					coords.push(next);
				}

				// Calculate height and width from outer edges
				newWidth = result.width = Math.abs(result.position.right - result.position.left);
				newHeight = result.height = Math.abs(result.position.bottom - result.position.top);

				// If it's the center corner...
				if(corner.abbrev() === 'c') {
					result.position = {
						left: result.position.left + result.width / 2,
						top: result.position.top + result.height / 2
					};
				}
				else {
					// Second pass, use a binary search algorithm to locate most suitable coordinate
					while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
					{
						newWidth = Math.floor(newWidth / 2);
						newHeight = Math.floor(newHeight / 2);

						if(corner.x === LEFT){ compareX = newWidth; }
						else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
						else{ compareX += Math.floor(newWidth / 2); }

						if(corner.y === TOP){ compareY = newHeight; }
						else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
						else{ compareY += Math.floor(newHeight / 2); }

						i = coords.length;
						while(i--)
						{
							if(coords.length < 2){ break; }

							realX = coords[i][0] - result.position.left;
							realY = coords[i][1] - result.position.top;

							if(
								corner.x === LEFT && realX >= compareX ||
								corner.x === RIGHT && realX <= compareX ||
								corner.x === CENTER && (realX < compareX || realX > result.width - compareX) ||
								corner.y === TOP && realY >= compareY ||
								corner.y === BOTTOM && realY <= compareY ||
								corner.y === CENTER && (realY < compareY || realY > result.height - compareY)) {
								coords.splice(i, 1);
							}
						}
					}
					result.position = { left: coords[0][0], top: coords[0][1] };
				}

				return result;
			},

			rect: function(ax, ay, bx, by) {
				return {
					width: Math.abs(bx - ax),
					height: Math.abs(by - ay),
					position: {
						left: Math.min(ax, bx),
						top: Math.min(ay, by)
					}
				};
			},

			_angles: {
				tc: 3 / 2, tr: 7 / 4, tl: 5 / 4,
				bc: 1 / 2, br: 1 / 4, bl: 3 / 4,
				rc: 2, lc: 1, c: 0
			},
			ellipse: function(cx, cy, rx, ry, corner) {
				var c = PLUGINS.polys._angles[ corner.abbrev() ],
					rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ),
					rys = ry * Math.sin( c * Math.PI );

				return {
					width: rx * 2 - Math.abs(rxc),
					height: ry * 2 - Math.abs(rys),
					position: {
						left: cx + rxc,
						top: cy + rys
					},
					adjustable: FALSE
				};
			},
			circle: function(cx, cy, r, corner) {
				return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
			}
		};
		;PLUGINS.svg = function(api, svg, corner)
		{
			var elem = svg[0],
				root = $(elem.ownerSVGElement),
				ownerDocument = elem.ownerDocument,
				strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2,
				frameOffset, mtx, transformed,
				len, next, i, points,
				result, position;

			// Ascend the parentNode chain until we find an element with getBBox()
			while(!elem.getBBox) { elem = elem.parentNode; }
			if(!elem.getBBox || !elem.parentNode) { return FALSE; }

			// Determine which shape calculation to use
			switch(elem.nodeName) {
				case 'ellipse':
				case 'circle':
					result = PLUGINS.polys.ellipse(
						elem.cx.baseVal.value,
						elem.cy.baseVal.value,
						(elem.rx || elem.r).baseVal.value + strokeWidth2,
						(elem.ry || elem.r).baseVal.value + strokeWidth2,
						corner
					);
					break;

				case 'line':
				case 'polygon':
				case 'polyline':
					// Determine points object (line has none, so mimic using array)
					points = elem.points || [
						{ x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
						{ x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
					];

					for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
						next = points.getItem ? points.getItem(i) : points[i];
						result.push.apply(result, [next.x, next.y]);
					}

					result = PLUGINS.polys.polygon(result, corner);
					break;

				// Unknown shape or rectangle? Use bounding box
				default:
					result = elem.getBBox();
					result = {
						width: result.width,
						height: result.height,
						position: {
							left: result.x,
							top: result.y
						}
					};
					break;
			}

			// Shortcut assignments
			position = result.position;
			root = root[0];

			// Convert position into a pixel value
			if(root.createSVGPoint) {
				mtx = elem.getScreenCTM();
				points = root.createSVGPoint();

				points.x = position.left;
				points.y = position.top;
				transformed = points.matrixTransform( mtx );
				position.left = transformed.x;
				position.top = transformed.y;
			}

			// Check the element is not in a child document, and if so, adjust for frame elements offset
			if(ownerDocument !== document && api.position.target !== 'mouse') {
				frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset();
				if(frameOffset) {
					position.left += frameOffset.left;
					position.top += frameOffset.top;
				}
			}

			// Adjust by scroll offset of owner document
			ownerDocument = $(ownerDocument);
			position.left += ownerDocument.scrollLeft();
			position.top += ownerDocument.scrollTop();

			return result;
		};
		;PLUGINS.imagemap = function(api, area, corner)
		{
			if(!area.jquery) { area = $(area); }

			var shape = (area.attr('shape') || 'rect').toLowerCase().replace('poly', 'polygon'),
				image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
				coordsString = area.attr('coords').trim(),
				coordsArray = coordsString.replace(/,$/, '').split(','),
				imageOffset, coords, i, result, len;

			// If we can't find the image using the map...
			if(!image.length) { return FALSE; }

			// Pass coordinates string if polygon
			if(shape === 'polygon') {
				result = PLUGINS.polys.polygon(coordsArray, corner);
			}

			// Otherwise parse the coordinates and pass them as arguments
			else if(PLUGINS.polys[shape]) {
				for(i = -1, len = coordsArray.length, coords = []; ++i < len;) {
					coords.push( parseInt(coordsArray[i], 10) );
				}

				result = PLUGINS.polys[shape].apply(
					this, coords.concat(corner)
				);
			}

			// If no shapre calculation method was found, return false
			else { return FALSE; }

			// Make sure we account for padding and borders on the image
			imageOffset = image.offset();
			imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2);
			imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2);

			// Add image position to offset coordinates
			result.position.left += imageOffset.left;
			result.position.top += imageOffset.top;

			return result;
		};
		;var IE6,

			/*
			 * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
			 * Special thanks to Brandon Aaron
			 */
			BGIFRAME = '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
				' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
				'-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>';

		function Ie6(api) {
			this._ns = 'ie6';

			this.qtip = api;
			this.init(api);
		}

		$.extend(Ie6.prototype, {
			_scroll : function() {
				var overlay = this.qtip.elements.overlay;
				overlay && (overlay[0].style.top = $(window).scrollTop() + 'px');
			},

			init: function(qtip) {
				var tooltip = qtip.tooltip;

				// Create the BGIFrame element if needed
				if($('select, object').length < 1) {
					this.bgiframe = qtip.elements.bgiframe = $(BGIFRAME).appendTo(tooltip);

					// Update BGIFrame on tooltip move
					qtip._bind(tooltip, 'tooltipmove', this.adjustBGIFrame, this._ns, this);
				}

				// redraw() container for width/height calculations
				this.redrawContainer = $('<div/>', { id: NAMESPACE+'-rcontainer' })
				.appendTo(document.body);

				// Fixup modal plugin if present too
				if( qtip.elements.overlay && qtip.elements.overlay.addClass('qtipmodal-ie6fix') ) {
					qtip._bind(window, ['scroll', 'resize'], this._scroll, this._ns, this);
					qtip._bind(tooltip, ['tooltipshow'], this._scroll, this._ns, this);
				}

				// Set dimensions
				this.redraw();
			},

			adjustBGIFrame: function() {
				var tooltip = this.qtip.tooltip,
					dimensions = {
						height: tooltip.outerHeight(FALSE),
						width: tooltip.outerWidth(FALSE)
					},
					plugin = this.qtip.plugins.tip,
					tip = this.qtip.elements.tip,
					tipAdjust, offset;

				// Adjust border offset
				offset = parseInt(tooltip.css('borderLeftWidth'), 10) || 0;
				offset = { left: -offset, top: -offset };

				// Adjust for tips plugin
				if(plugin && tip) {
					tipAdjust = plugin.corner.precedance === 'x' ? [WIDTH, LEFT] : [HEIGHT, TOP];
					offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
				}

				// Update bgiframe
				this.bgiframe.css(offset).css(dimensions);
			},

			// Max/min width simulator function
			redraw: function() {
				if(this.qtip.rendered < 1 || this.drawing) { return this; }

				var tooltip = this.qtip.tooltip,
					style = this.qtip.options.style,
					container = this.qtip.options.position.container,
					perc, width, max, min;

				// Set drawing flag
				this.qtip.drawing = 1;

				// If tooltip has a set height/width, just set it... like a boss!
				if(style.height) { tooltip.css(HEIGHT, style.height); }
				if(style.width) { tooltip.css(WIDTH, style.width); }

				// Simulate max/min width if not set width present...
				else {
					// Reset width and add fluid class
					tooltip.css(WIDTH, '').appendTo(this.redrawContainer);

					// Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!)
					width = tooltip.width();
					if(width % 2 < 1) { width += 1; }

					// Grab our max/min properties
					max = tooltip.css('maxWidth') || '';
					min = tooltip.css('minWidth') || '';

					// Parse into proper pixel values
					perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
					max = (max.indexOf('%') > -1 ? perc : 1 * parseInt(max, 10)) || width;
					min = (min.indexOf('%') > -1 ? perc : 1 * parseInt(min, 10)) || 0;

					// Determine new dimension size based on max/min/current values
					width = max + min ? Math.min(Math.max(width, min), max) : width;

					// Set the newly calculated width and remvoe fluid class
					tooltip.css(WIDTH, Math.round(width)).appendTo(container);
				}

				// Set drawing flag
				this.drawing = 0;

				return this;
			},

			destroy: function() {
				// Remove iframe
				this.bgiframe && this.bgiframe.remove();

				// Remove bound events
				this.qtip._unbind([window, this.qtip.tooltip], this._ns);
			}
		});

		IE6 = PLUGINS.ie6 = function(api) {
			// Proceed only if the browser is IE6
			return BROWSER.ie === 6 ? new Ie6(api) : FALSE;
		};

		IE6.initialize = 'render';

		CHECKS.ie6 = {
			'^content|style$': function() {
				this.redraw();
			}
		};
		;}));
}( window, document ));
