;(function($){ 

$.fn._hover = $.fn.hover;

$.fn.hover = function( fn1, fn2, fn3 ) {
	if ( fn3 ) this.bind('hoverstart', fn1 ); // 3 args
	if ( fn2 ) this.bind('hoverend', fn3 ? fn3 : fn2 ); // 2+ args
	return !fn1 ? this.trigger('hover') // 0 args 
		: this.bind('hover', fn3 ? fn2 : fn1 ); // 1+ args
	};	

var hover = $.event.special.hover = {
	delay: 100, // milliseconds
	speed: 100, // pixels per second
	setup: function( data ){
		data = $.extend({ speed: hover.speed, delay: hover.delay, hovered:0 }, data||{} );
		$.event.add( this, "mouseenter mouseleave", hoverHandler, data );
		},
	teardown: function(){
		$.event.remove( this, "mouseenter mouseleave", hoverHandler );
		}
	};

function hoverHandler( event ){
	var data = event.data || event;
	switch ( event.type ){
		case 'mouseenter':
			data.dist2 = 0; 
			data.event = event; 
			event.type = "hoverstart"; 
			if ( $.event.handle.call( this, event ) !== false ){ 
				data.elem = this; 
				$.event.add( this, "mousemove", hoverHandler, data ); 
				data.timer = setTimeout( compare, data.delay );
				}
			break;
		case 'mousemove': 
			data.dist2 += Math.pow( event.pageX-data.event.pageX, 2 ) 
				+ Math.pow( event.pageY-data.event.pageY, 2 ); 
			data.event = event; 
			break;
		case 'mouseleave': 
			clearTimeout( data.timer );
			if ( data.hovered ){ 
				event.type = "hoverend"; 
				$.event.handle.call( this, event ); 
				data.hovered--; 
				}
			else $.event.remove( data.elem, "mousemove", hoverHandler ); 
			break;
		default: 
			if ( data.dist2 <= Math.pow( data.speed*( data.delay/1e3 ), 2 ) ){ 
				$.event.remove( data.elem, "mousemove", hoverHandler ); 
				data.event.type = "hover"; 
				if ( $.event.handle.call( data.elem, data.event ) !== false ) 
					data.hovered++; 
				}
			else data.timer = setTimeout( compare, data.delay ); 
			data.dist2 = 0; 
			break;
		}
	function compare(){ hoverHandler( data ); }; 
	};
	
})(jQuery); 