/**
 *  Устранение мигания фоновых картинок в IE6
 */

$( function() {
	try {
//		document.execCommand( 'BackgroundImageCache', false, true );
	} catch( e ) {
		
	}
} ); 




/**
sprintf() for JavaScript 0.7-beta1
http://www.diveintojavascript.com/projects/javascript-sprintf

Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of sprintf() for JavaScript nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Changelog:
2010.09.06 - 0.7-beta1
  - features: vsprintf, support for named placeholders
  - enhancements: format cache, reduced global namespace pollution

2010.05.22 - 0.6:
 - reverted to 0.4 and fixed the bug regarding the sign of the number 0
 Note:
 Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
 who warned me about a bug in 0.5, I discovered that the last update was
 a regress. I appologize for that.

2010.05.09 - 0.5:
 - bug fix: 0 is now preceeded with a + sign
 - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
 - switched from GPL to BSD license

2007.10.21 - 0.4:
 - unit test and patch (David Baird)

2007.09.17 - 0.3:
 - bug fix: no longer throws exception on empty paramenters (Hans Pufal)

2007.09.11 - 0.2:
 - feature: added argument swapping

2007.04.03 - 0.1:
 - initial release
**/

var sprintf = (function() {
	function get_type(variable) {
		return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
	}
	function str_repeat(input, multiplier) {
		for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
		return output.join('');
	}

	var str_format = function() {
		if (!str_format.cache.hasOwnProperty(arguments[0])) {
			str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
		}
		return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
	};

	str_format.format = function(parse_tree, argv) {
		var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
		for (i = 0; i < tree_length; i++) {
			node_type = get_type(parse_tree[i]);
			if (node_type === 'string') {
				output.push(parse_tree[i]);
			}
			else if (node_type === 'array') {
				match = parse_tree[i]; // convenience purposes only
				if (match[2]) { // keyword argument
					arg = argv[cursor];
					for (k = 0; k < match[2].length; k++) {
						if (!arg.hasOwnProperty(match[2][k])) {
							throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
						}
						arg = arg[match[2][k]];
					}
				}
				else if (match[1]) { // positional argument (explicit)
					arg = argv[match[1]];
				}
				else { // positional argument (implicit)
					arg = argv[cursor++];
				}

				if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
					throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
				}
				switch (match[8]) {
					case 'b': arg = arg.toString(2); break;
					case 'c': arg = String.fromCharCode(arg); break;
					case 'd': arg = parseInt(arg, 10); break;
					case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
					case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
					case 'o': arg = arg.toString(8); break;
					case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
					case 'u': arg = Math.abs(arg); break;
					case 'x': arg = arg.toString(16); break;
					case 'X': arg = arg.toString(16).toUpperCase(); break;
				}
				arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
				pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
				pad_length = match[6] - String(arg).length;
				pad = match[6] ? str_repeat(pad_character, pad_length) : '';
				output.push(match[5] ? arg + pad : pad + arg);
			}
		}
		return output.join('');
	};

	str_format.cache = {};

	str_format.parse = function(fmt) {
		var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
		while (_fmt) {
			if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
				parse_tree.push(match[0]);
			}
			else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
				parse_tree.push('%');
			}
			else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
				if (match[2]) {
					arg_names |= 1;
					var field_list = [], replacement_field = match[2], field_match = [];
					if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
						field_list.push(field_match[1]);
						while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
							if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
								field_list.push(field_match[1]);
							}
							else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
								field_list.push(field_match[1]);
							}
							else {
								throw('[sprintf] huh?');
							}
						}
					}
					else {
						throw('[sprintf] huh?');
					}
					match[2] = field_list;
				}
				else {
					arg_names |= 2;
				}
				if (arg_names === 3) {
					throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
				}
				parse_tree.push(match);
			}
			else {
				throw('[sprintf] huh?');
			}
			_fmt = _fmt.substring(match[0].length);
		}
		return parse_tree;
	};

	return str_format;
})();

var vsprintf = function(fmt, argv) {
	argv.unshift(fmt);
	return sprintf.apply(null, argv);
};







/**
 *  Добавим в jquery событие mousewheel
 */

( function( jQuery ) {
	
	var types = [ 'DOMMouseScroll', 'mousewheel' ];
	
	jQuery.event.special.mousewheel = {
		setup: function() {
			if ( this.addEventListener )
				for ( var i=types.length; i; )
					this.addEventListener( types[ --i ], handler, false );
			else
				this.onmousewheel = handler;
		},
		teardown: function() {
			if ( this.removeEventListener )
				for ( var i=types.length; i; )
					this.removeEventListener( types[ --i ], handler, false );
			else
				this.onmousewheel = null;
		}
	}
	
	jQuery.fn.extend( {
		mousewheel: function( fn ) {
			return fn ? this.bind( "mousewheel", fn ) : this.trigger( "mousewheel" );
		},
		unmousewheel: function( fn ) {
			return this.unbind( "mousewheel", fn );
		}
	} );
	
	function handler( event ) {
		var args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true;
		event = jQuery.event.fix( event || window.event );
		event.type = "mousewheel";
		if ( event.wheelDelta )
			delta = event.wheelDelta / 120;
		if ( event.detail )
			delta = -event.detail / 3;
		// Add events and delta to the front of the arguments
		args.unshift( event, delta );
		return jQuery.event.handle.apply( this, args );
	}
})( jQuery );



/**
 *  Добавим к jquery методы:
 *  
 *  escape( value ) - экранирует строку для url, используя encodeURIComponent или escape
 *  unescape( value ) - обратное действие к escape
 *  escapeHTML( value ) - аналог функции php htmlspecialchars
 *  unescapeHTML( value ) - обратное действие к escapeHTML
 *  toRelativeURL( string ) - превращает url из вида "http://www.domain.ru/path/file" в "/path/file"
 *  maxZIndex( parentNode ) - возвращает наибольшее установленное значение z-index (поиск ведется по всем дочерним элементам к parentNode)
 *  	если parentNode = null, то поиск ведется по всему документу
 *  pageSize() - возвращает полный размер документа в px
 *  viewPortSize() - возвращает размер рабочей области браузера в px
 *  ajaxFileUpload - TODO документировать
 *  ajaxFormPost - TODO документировать
 *  
 */

jQuery.extend({
	
		generateUID : function () {
			arguments.callee.uid = arguments.callee.uid || 0;
			arguments.callee.uid++;
			return 'uid' + arguments.callee.uid + Math.round( Math.random() * 100 );
		},

		escape : function ( value ) {
			return window.encodeURIComponent ? encodeURIComponent ( value ) : escape ( value )
		},
		
		unescape : function ( value ) {
			return window.decodeURIComponent ? decodeURIComponent ( value ) : unescape ( value )
		},
		
		escapeHTML : function ( value ) {
			return
				value.replace( /</g, "&lt;" ).replace( />/g, "&gt;" ).replace( /&/g, "&amp;" ).replace( /"/g, "&quot;" ).replace( /'/g, "&#39;" );
		},

		unescapeHTML : function ( value ) {
			value = value.replace( /&lt;/g, "<" ).replace( /&gt;/g, ">" );
			return value.replace( /&quot;/g, '"' ).replace( /&#39;/g, "'" );
		},
		
		toRelativeURL : function( string ) {
			if ( string.charAt ( 0 ) == '/' )
				return string;
			if ( ( string.indexOf ( '://' ) != -1 ) ) {
				var a = string.split ( '/' ), ret = '', i;
				for ( i = 3; i < a.length; i++ )
					ret += '/'+ a[ i ];
				return ret;
			}
			return string;
		},

		maxZIndex : function ( parentNode ) {
			parentNode = parentNode || document;
			var zIndex = 0, i, z;
			jQuery( parentNode ).find( '*' ).each(
				function() {
					var z = parseInt( jQuery.css( this, "z-index" ) ); 
					if ( z > zIndex ) zIndex = z;
				}
			);
			return zIndex;
		},

		pageSize : function () {
			var b = document.documentElement && !jQuery.browser.opera ? document.documentElement : document.body;
			return {
				width : Math.max( b.scrollWidth, b.offsetWidth, b.clientWidth ),
				height : Math.max( b.scrollHeight, b.offsetHeight, b.clientHeight )
			}
		},
		
		viewPortSize : function ( w ) {
			w = w || window;
			var result
			if ( jQuery.browser.safari && !document.evaluate ) {
				// Safari <3.0 needs self.innerWidth/Height
				result = { width : self.innerWidth, height : self.innerHeight }
			} else if ( jQuery.browser.opera && parseFloat( window.opera.version() ) < 9.5 ) {
				// Opera <9.5 needs document.body.clientWidth/Height
				result = { width : document.body.clientWidth, height : document.body.clientHeight }
			} else {
				result = { width : document.documentElement.clientWidth, height : document.documentElement.clientHeight }
			}
			result[ 'left' ] = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
			result[ 'top' ] = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
			result[ 'centerLeft' ] = parseInt( result.width / 2 ) + result.left;
			result[ 'centerTop' ] = parseInt( result.height / 2 ) + result.top;
			return result;
		},
		
		ajaxFileUpload : function ( s ) {
			s = jQuery.extend( {}, jQuery.ajaxSettings, s );
			arguments.callee.iuid = arguments.callee.iuid || 0;
			var
				xml = {
					isIframe : true
				},
				requestDone = false,
				id = 'form_pf_' + arguments.callee.iuid++,
				frameId = 'jUploadFrame' + id,
				formId = 'jUploadForm' + id,
				form = jQuery( '<form name="' + formId + '" id="' + formId + '" method="POST" encoding="multipart/form-data" enctype="multipart/form-data" action="' + s.url + '" target="' + frameId + '"/>' ).appendTo( jQuery( '<div style="display:none;"><iframe name="' + frameId + '" id="' + frameId + '" src="about:blank" style="display:none;"> </iframe></div>' ).appendTo( 'body' ) );
			/*
			jQuery( form ).attr( 'action', s.url );
			jQuery( form ).attr( 'method', 'POST' );
			jQuery( form ).attr( 'target', frameId );
			if ( form.encoding ) {
				form.encoding = 'multipart/form-data';
			} else {
				form.enctype = 'multipart/form-data';
			} */

			// Watch for a new set of requests
			if ( s.global && ! jQuery.active++ ) {
				jQuery.event.trigger( "ajaxStart" );
			}
			// Create the request object
			if ( s.global )
				jQuery.event.trigger( "ajaxSend", [ xml, s ] );
			//cloning file elements to form
			jQuery( s.fileElems ).each(
				function() {
					this.parentNode.insertBefore( this.cloneNode( false ), this.nextSibling );
					form.append( this );	
				}
			);
			//building input elemets for data
			s.data = s.data || {};
			if ( $.isArray( s.data ) && !!s.data.length ) {
				//serialized by serializeArray
				function add( key, value ){
					jQuery( '<input type="text" name="' + key + '" id="' + key + '" value="' + value.replace(/\"/g, "&quot;") + '"/>' ).appendTo( form );		
				};
				$.each( s.data, function(){ add( this.name, this.value ) } );
			} else if ( typeof s.data === "object" && !$.isArray( s.data ) ) {
				//just object {}
				for( var i in ( s.data || {} ) ) {
					jQuery( '<input type="text" name="' + i + '" id="' + i + '" value="' + s.data[ i ].replace(/\"/g, "&quot;") + '"/>' ).appendTo( form );		
				}
			}
				
			// Wait for a response to come back
			var uploadCallback = function( isTimeout ) {
				var io = document.getElementById( frameId );
				try {
					if( io.contentDocument && io.contentDocument.document ) {
						xml.responseText = io.contentDocument.document.body ? io.contentDocument.document.body.innerHTML : null;
						xml.responseXML =
							io.contentDocument.document.XMLDocument ? io.contentDocument.document.XMLDocument :
								io.contentDocument.document;
					} else if ( io.contentWindow && io.contentWindow.document ) {
						xml.responseText = io.contentWindow.document.body ? io.contentWindow.document.body.innerHTML : null;
						xml.responseXML =
							io.contentWindow.document.XMLDocument ? io.contentWindow.document.XMLDocument :
								io.contentWindow.document;
					}
				} catch( e ) {
					jQuery.handleError( s, xml, null, e );
				}
				if ( xml || isTimeout == "timeout" ) {
					requestDone = true;
					var status;
					try {
						status = isTimeout != "timeout" ? "success" : "error";
						// Make sure that the request was successful or notmodified
						if ( status != "error" ) {
							// process the data (runs the xml through httpData regardless of callback)
							var data = !s.dataType;
							if ( s.dataType == "xml" || data ) {
								data = xml.responseXML;
							} else {
								//Opera add <pre> at begin only
								//IE add <pre> at begin and </pre> at end
								//Safary/Chrome add <pre.......> at begin and </pre> at end
								data = xml.responseText.replace( /^<pre[^>]*>/ig, '' );
								data = data.replace( /<\/pre>$/ig, '' );
								data = jQuery.unescapeHTML( data );
								xml.responseText = data;
							}
							// If the s.dataType is "script", eval it in global context
							if ( s.dataType == "script" ) jQuery.globalEval( data );
							// Get the JavaScript object, if JSON is used.
							if ( s.dataType == "json" ) eval( "data = " + data );
							// evaluate scripts within html
							if ( s.dataType == "html" ) jQuery( "<div>" ).html( data ).evalScripts();
							// If a local callback was specified, fire it and pass it the data
							if ( s.success )
								s.success( data, status );
							// Fire the global callback
							if( s.global )
								jQuery.event.trigger( "ajaxSuccess", [ xml, s ] );
						} else
							jQuery.handleError( s, xml, status );
					} catch( e ) {
						status = "error";
						jQuery.handleError( s, xml, status, e );
					}
					// The request was completed
					if( s.global )
						jQuery.event.trigger( "ajaxComplete", [ xml, s ] );
					// Handle the global AJAX counter
					if ( s.global && ! --jQuery.active )
						jQuery.event.trigger( "ajaxStop" );
					// Process result
					if ( s.complete )
						s.complete( xml, status );
					jQuery( io ).unbind()
					setTimeout( function() {
							try {
								jQuery( io ).remove();
								jQuery( cont ).remove();
							} catch( e ) {
								jQuery.handleError( s, xml, null, e );
							}
						}, 100 );
					xml = null;
				}
			}
			// Timeout checker
			if ( s.timeout > 0 ) {
				setTimeout( function(){
					// Check to see if the request is still happening
					if( !requestDone )
						uploadCallback( "timeout" );
				}, s.timeout );
			}
			if( window.attachEvent ) {
				document.getElementById( frameId ).attachEvent( 'onload', uploadCallback );
			} else {
				document.getElementById( frameId ).addEventListener( 'load', uploadCallback, false );
			}
			try {
				jQuery( form ).submit();
			} catch( e ) {
				jQuery.handleError( s, xml, null, e );
			}
			return { abort: function () {} };
	},
	
	
	ajaxFormPost : function ( formElem, callback, url ) {
		var
			serializedForm = $( formElem ).serializeArray(),
			fileElems = $( formElem ).find( "input[type='file']" );
		if ( callback )
			serializedForm = callback( 'data', serializedForm ) || serializedForm;
		if ( fileElems.length ) {
			if ( callback )
				callback( 'send' );
			$.ajaxFileUpload( {
				fileElems: fileElems,
				url: url,
				data: serializedForm,
				success: callback ? function ( response ) { callback( 'success', response ) } : null,
				dataType: 'text'
			} );
		} else {
			if ( !jQuery.ajaxSettings.uniqueCounter )
				jQuery.ajaxSettings.uniqueCounter = 1;
			jQuery.ajaxSettings.uniqueCounter++;
			var
				boundaryString = "qQ" + jQuery.ajaxSettings.uniqueCounter + Math.round( Math.random() * 100 ) +"zZ",
				boundary = '--'+ boundaryString,
				s = [];
			if ( $.isArray( serializedForm ) && !!serializedForm.length ) {
				function add( key, value ){
					// key'%5B %5D'
					s[ s.length ] =
						'Content-Disposition: form-data; name="'+ encodeURIComponent( key ).replace( /%5B/gi, '[' ).replace( /%5D/gi, ']' ) +'"'+'\n'+
						'Content-Type: text/plain'+'\n'+
						'\n'+
						encodeURIComponent( value );
				};
				$.each( serializedForm, function(){
					add( this.name, this.value );
				});
			}
			serializedForm = boundary +'\n'+ s.join ( '\n'+ boundary +'\n' ) +'\n'+ boundary +'\n';
			if ( callback )
				callback( 'send' );
			$.ajax( {
				type: "POST",
				url: url,
				processData: false,
				data: serializedForm,
				success: callback ? function ( response ) { callback( 'success', response ) } : null,
				dataType: 'text',
				contentType : "multipart/form-data; boundary=\""+ boundaryString +"\""
			} );

		}
	}
	
});





jQuery.fn.positionedOffset = function() {
	var offsetParent = this.offsetParent(), offset = this.offset(), position = this.position();
	if ( !/^body|html$/i.test( offsetParent[ 0 ].tagName ) ) {
		return { left : position.left, top : position.top, from : offsetParent }
	} else {
		return { left : offset.left, top : offset.top, from : offsetParent }
	}
}


jQuery.fn.absolutize = function() {
	
	if ( arguments[ 1 ] ) {
		var correction = { left : arguments[ 0 ], top : arguments[ 1 ] }
	} else {
		var correction = arguments[ 0 ] || { left : 0, top : 0 }
	}

  return this.each( function() {
    var element = jQuery( this );
    if ( element.css( 'position' ) == 'absolute' ) {
      return element;
    }

    var offsets = element.positionedOffset();
    var width = element[ 0 ].clientWidth;
    var height = element[ 0 ].clientHeight;

    element._originalPosition = element.css( "position" );
    element._originalLeft = offsets.left - parseFloat( element.css( "left" ) || 0 );
    element._originalTop = offsets.top - parseFloat( element.css( "top" ) || 0 );
    element._originalWidth = element.css( "width" );
    element._originalHeight = element.css( "height" );

    element.css( "position", "absolute" );
    element.css( "top", offsets.top + correction.top + 'px' );
    element.css( "left", offsets.left + correction.left + 'px' );
    element.css( "width", width + 'px' );
    element.css( "height", height + 'px' );
    return element;

  });
}



/**
 * Cookie plugin
 *
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */

/**
 * Create a cookie with the given name and value and other optional parameters.
 *
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Set the value of a cookie.
 * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
 * @desc Create a cookie with all available options.
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Create a session cookie.
 * @example $.cookie('the_cookie', null);
 * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
 *       used when the cookie was set.
 *
 * @param String name The name of the cookie.
 * @param String value The value of the cookie.
 * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
 * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
 *                             If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
 *                             If set to null or omitted, the cookie will be a session cookie and will not be retained
 *                             when the the browser exits.
 * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
 * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
 * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
 *                        require a secure protocol (like HTTPS).
 * @type undefined
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

/**
 * Get the value of a cookie with the given name.
 *
 * @example $.cookie('the_cookie');
 * @desc Get the value of a cookie.
 *
 * @param String name The name of the cookie.
 * @return The value of the cookie.
 * @type String
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
jQuery.cookie = function(name, value, options) {
    if (typeof value != 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
            value = '';
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
            var date;
            if (typeof options.expires == 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // CAUTION: Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = '; path=' + ( options.path ? options.path : '/' );
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
}



//Thanks to Alexandre Gomes
jQuery( function(){
	var inner = document.createElement( 'p' );
	inner.style.width = "100%";
	inner.style.height = "200px";
	var outer = document.createElement( 'div' );
	outer.style.position = "absolute";
	outer.style.top = "0px";
	outer.style.left = "0px";
	outer.style.visibility = "hidden";
	outer.style.width = "200px";
	outer.style.height = "150px";
	outer.style.overflow = "hidden";
	outer.appendChild( inner );
	document.body.appendChild ( outer );
	var w1 = inner.offsetWidth;
	var h1 = inner.offsetHeight;	
	outer.style.overflow = 'scroll';
	var w2 = inner.offsetWidth;
	var h2 = inner.offsetHeight;
	if (w1 == w2) w2 = outer.clientWidth;
	if (h1 == h2) h2 = outer.clientWidth;
	document.body.removeChild( outer );
	window.scrollbarWidth = w1-w2;
	window.scrollbarHeight = h1-h2;
} );


jQuery.tableRowHighlighter = {
	
	build : function ( options ) {

		options = jQuery.extend( {
                onHoverClass: 'hovered',
                cellsSelector: 'td'
            }, options || {} );
						
		this.find( options.cellsSelector )
			.each( function() {
				this._tableRowHighlighterOptions = jQuery.extend( options, {} );
			} )
			.bind( 'mouseover', jQuery.tableRowHighlighter._tr_hover_helper )
			.bind( 'mouseout', jQuery.tableRowHighlighter._tr_unhover_helper );
		return this;
	},
	

	_tr_hover_helper : function ( ev ) {
		$( this ).parent().addClass( this._tableRowHighlighterOptions.onHoverClass );
	},

	_tr_unhover_helper : function ( ev ) {
		$( this ).parent().removeClass( this._tableRowHighlighterOptions.onHoverClass );
	}
	
}

jQuery.fn.extend(
	{
		tableRowHighlighter : jQuery.tableRowHighlighter.build
	}
);



/**
 * $.include - script inclusion jQuery plugin
 * Based on idea from http://www.gnucitizen.org/projects/jquery-include/
 * @author Tobiasz Cudnik
 * @link http://meta20.net/.include_script_inclusion_jQuery_plugin
 * @license MIT
 */
// overload jquery's onDomReady
if ( jQuery.browser.mozilla || jQuery.browser.opera ) {
	document.removeEventListener( "DOMContentLoaded", jQuery.ready, false );
	document.addEventListener( "DOMContentLoaded", function(){ jQuery.ready(); }, false );
}
jQuery.event.remove( window, "load", jQuery.ready );
jQuery.event.add( window, "load", function(){ jQuery.ready(); } );
jQuery.extend({
	includeStates: {},
	include: function(url, callback, dependency){
		if ( typeof callback != 'function' && ! dependency ) {
			dependency = callback;
			callback = null;
		}
		url = url.replace('\n', '');
		jQuery.includeStates[url] = false;
		var script = document.createElement('script');
		script.type = 'text/javascript';
		script.onload = function () {
			jQuery.includeStates[url] = true;
			if ( callback )
				callback.call(script);
		};
		script.onreadystatechange = function () {
			if ( this.readyState != "complete" && this.readyState != "loaded" ) return;
			jQuery.includeStates[url] = true;
			if ( callback )
				callback.call(script);
		};
		script.src = url;
		if ( dependency ) {
			if ( dependency.constructor != Array )
				dependency = [dependency];
			setTimeout(function(){
				var valid = true;
				$.each(dependency, function(k, v){
					if (! v() ) {
						valid = false;
						return false;
					}
				})
				if ( valid )
					document.getElementsByTagName('head')[0].appendChild(script);
				else
					setTimeout(arguments.callee, 10);
			}, 10);
		}
		else
			document.getElementsByTagName('head')[0].appendChild(script);
		return function(){
			return jQuery.includeStates[url];
		}
	},
	readyOld: jQuery.ready,
	ready: function () {
		if (jQuery.isReady) return;
		imReady = true;
		$.each(jQuery.includeStates, function(url, state) {
			if (! state)
				return imReady = false;
		});
		if (imReady) {
			jQuery.readyOld.apply(jQuery, arguments);
		} else {
			setTimeout(arguments.callee, 10);
		}
	}
});

//Absolutize
(function($){
	jQuery.fn.absolutize = function() {
		return this.each(function(){
		    
			var elem = $(this);
		    if (elem.css('position') == 'absolute')
		      return elem;
			
		    elem.css("top", elem.offset().top);
		    elem.css("left", elem.offset().left);
		    elem.css("position", "absolute");
        });
	}
})(jQuery);





/*
 * jQuery Caret Range plugin
 * Copyright (c) 2009 Matt Zabriskie
 * Released under the MIT and GPL licenses.
 *
 */

/* USAGE
 * 
var input = $("#selector");
var range = input.caret();
var text = null;

// Get selected text
text = input.val().substr(range.start, range.end - 1);

// Insert text at caret then restore caret
var value = input.val();
text = " New Text ";
input.val(value.substr(0, range.start) + text + value.substr(range.end, value.length));
input.caret(range.start + text.length);

// Select first ten characters of text
input.caret(0, 10);

*/



(function($) {
	$.extend($.fn, {
		caret: function (start, end) {
			var elem = this[0];

			if (elem) {							
				// get caret range
				if (typeof start == "undefined") {
					if (elem.selectionStart) {
						start = elem.selectionStart;
						end = elem.selectionEnd;
					}
					else if (document.selection) {
						var val = this.val();
						var range = document.selection.createRange().duplicate();
						range.moveEnd("character", val.length)
						start = (range.text == "" ? val.length : val.lastIndexOf(range.text));

						range = document.selection.createRange().duplicate();
						range.moveStart("character", -val.length);
						end = range.text.length;
					}
					//я добавил, т.к. вместо нуля шли underfined
					if (typeof start != "number") start = 0;
					if (typeof end != "number") end = 0;
				}
				// set caret range
				else {
					var val = this.val();

					if (typeof start != "number") start = -1;
					if (typeof end != "number") end = -1;
					if (start < 0) start = 0;
					if (end > val.length) end = val.length;
					if (end < start) end = start;
					if (start > end) start = end;

					elem.focus();

					if (elem.selectionStart) {
						elem.selectionStart = start;
						elem.selectionEnd = end;
					}
					else if (document.selection) {
						var range = elem.createTextRange();
						range.collapse(true);
						range.moveStart("character", start);
						range.moveEnd("character", end - start);
						range.select();
					}
				}

				return {start:start, end: end};
			}
		}
	});
})(jQuery);




jQuery.fn.maxScrollPosition = function() {
	return { left : ( this.get(0).scrollWidth - this.get(0).clientWidth ), top : ( this.get(0).scrollHeight - this.get(0).clientHeight ) };
}






/*
 * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
 *
 * Uses the built in easing capabilities added In jQuery 1.1
 * to offer multiple easing options
 *
 * TERMS OF USE - jQuery Easing
 * 
 * Open source under the BSD License. 
 * 
 * Copyright В© 2008 George McGinley Smith
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
*/

// t: current time, b: begInnIng value, c: change In value, d: duration
jQuery.easing['jswing'] = jQuery.easing['swing'];

jQuery.extend( jQuery.easing,
{
	def: 'easeOutCubic',
	swing: function (x, t, b, c, d) {
		//alert(jQuery.easing.default);
		return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
	},
	easeInQuad: function (x, t, b, c, d) {
		return c*(t/=d)*t + b;
	},
	easeOutQuad: function (x, t, b, c, d) {
		return -c *(t/=d)*(t-2) + b;
	},
	easeInOutQuad: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	},
	easeInCubic: function (x, t, b, c, d) {
		return c*(t/=d)*t*t + b;
	},
	easeOutCubic: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t + 1) + b;
	},
	easeInOutCubic: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t + b;
		return c/2*((t-=2)*t*t + 2) + b;
	},
	easeInQuart: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t + b;
	},
	easeOutQuart: function (x, t, b, c, d) {
		return -c * ((t=t/d-1)*t*t*t - 1) + b;
	},
	easeInOutQuart: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
		return -c/2 * ((t-=2)*t*t*t - 2) + b;
	},
	easeInQuint: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t*t + b;
	},
	easeOutQuint: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t*t*t + 1) + b;
	},
	easeInOutQuint: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	},
	easeInSine: function (x, t, b, c, d) {
		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
	},
	easeOutSine: function (x, t, b, c, d) {
		return c * Math.sin(t/d * (Math.PI/2)) + b;
	},
	easeInOutSine: function (x, t, b, c, d) {
		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
	},
	easeInExpo: function (x, t, b, c, d) {
		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
	},
	easeOutExpo: function (x, t, b, c, d) {
		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
	},
	easeInOutExpo: function (x, t, b, c, d) {
		if (t==0) return b;
		if (t==d) return b+c;
		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
	},
	easeInCirc: function (x, t, b, c, d) {
		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
	},
	easeOutCirc: function (x, t, b, c, d) {
		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
	},
	easeInOutCirc: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
	},
	easeInElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
	},
	easeOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
	},
	easeInOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
		return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
	},
	easeInBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*(t/=d)*t*((s+1)*t - s) + b;
	},
	easeOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
	},
	easeInOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158; 
		if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
		return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
	},
	easeInBounce: function (x, t, b, c, d) {
		return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
	},
	easeOutBounce: function (x, t, b, c, d) {
		if ((t/=d) < (1/2.75)) {
			return c*(7.5625*t*t) + b;
		} else if (t < (2/2.75)) {
			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
		} else if (t < (2.5/2.75)) {
			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
		} else {
			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
		}
	},
	easeInOutBounce: function (x, t, b, c, d) {
		if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
		return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
	}
});

/*
 *
 * TERMS OF USE - EASING EQUATIONS
 * 
 * Open source under the BSD License. 
 * 
 * Copyright В© 2001 Robert Penner
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
 */



/*
 * чтобы свершилось ниже, надо:
 * 
 *	type: "POST"
 *	processData: false
 *	contentType : "multipart/form-data"
 *
 */

jQuery.extend( jQuery.ajaxSettings, {
	beforeSend : function ( xhr ) {
		if ( ( this.type.toUpperCase() == 'POST' ) && ( this.contentType == 'multipart/form-data' ) && !this.processData ) {
			if ( !jQuery.ajaxSettings.uniqueCounter )
				jQuery.ajaxSettings.uniqueCounter = 1;
			jQuery.ajaxSettings.uniqueCounter++;
			var
				boundaryString = "qQ" + jQuery.ajaxSettings.uniqueCounter + Math.round( Math.random() * 100 ) +"zZ",
				boundary = '--'+ boundaryString,
				s = [];
			if ( typeof this.data === "string" && !!this.data.length ) {
				s.push( 
					'Content-Disposition: form-data; name="query"'+'\n'+
					'Content-Type: text/plain'+'\n'+
					'\n'+
					encodeURIComponent( this.data )
				); 
			}
			if ( $.isArray( this.data ) && !!this.data.length ) {
				function add( key, value ){
					// key'%5B %5D'
					s[ s.length ] =
						'Content-Disposition: form-data; name="'+ encodeURIComponent( key ).replace( /%5B/gi, '[' ).replace( /%5D/gi, ']' ) +'"'+'\n'+
						'Content-Type: text/plain'+'\n'+
						'\n'+
						encodeURIComponent( value );
				};
				$.each( this.data, function(){
					add( this.name, this.value );
				});
			}
			if ( typeof this.data === "object" && !$.isArray( this.data ) ) {
				for ( var x in this.data )
					if ( !$.isFunction( this.data[ x ] ) ) {
						if ( typeof this.data[ x ] === 'boolean' )
							this.data[ x ] = this.data[ x ] ? 1 : 0;
						s.push( 
							'Content-Disposition: form-data; name="'+ x +'"'+'\n'+
							'Content-Type: text/plain'+'\n'+
							'\n'+
							( this.data[ x ] === undefined ? 'undefined' : encodeURIComponent( this.data[ x ] + '' ) )
						);
					}
			}
			this.data = boundary +'\n'+ s.join ( '\n'+ boundary +'\n' ) +'\n'+ boundary +'\n';
			xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=\""+ boundaryString +"\"" );
		}	
	}
} );



/*
 *	Tabby jQuery plugin version 0.12
 *
 *	Ted Devito - http://teddevito.com/demos/textarea.html
 *
 *	Copyright (c) 2009 Ted Devito
 *	 
 *	Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 
 *	conditions are met:
 *	
 *		1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *		2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer  
 *			in the documentation and/or other materials provided with the distribution.
 *		3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written 
 *			permission. 
 *	 
 *	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE 
 *	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 *	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 *	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 *	OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
 
// create closure

(function($) {
 
	// plugin definition

	$.fn.tabby = function(options) {
		//debug(this);
		// build main options before element iteration
		var opts = $.extend({}, $.fn.tabby.defaults, options);
		var pressed = $.fn.tabby.pressed; 
		
		// iterate and reformat each matched element
		return this.each(function() {
			$this = $(this);
			
			// build element specific options
			var options = $.meta ? $.extend({}, opts, $this.data()) : opts;
			
			$this.bind('keydown',function (e) {
				var kc = $.fn.tabby.catch_kc(e);
				if (16 == kc) pressed.shft = true;
				/*
				because both CTRL+TAB and ALT+TAB default to an event (changing tab/window) that 
				will prevent js from capturing the keyup event, we'll set a timer on releasing them.
				*/
				if (17 == kc) {pressed.ctrl = true;	setTimeout("$.fn.tabby.pressed.ctrl = false;",1000);}
				if (18 == kc) {pressed.alt = true; 	setTimeout("$.fn.tabby.pressed.alt = false;",1000);}
					
				if (9 == kc && !pressed.ctrl && !pressed.alt) {
					e.preventDefault; // does not work in O9.63 ??
					pressed.last = kc;	setTimeout("$.fn.tabby.pressed.last = null;",0);
					process_keypress ($(e.target).get(0), pressed.shft, options);
					return false;
				}
				
			}).bind('keyup',function (e) {
				if (16 == $.fn.tabby.catch_kc(e)) pressed.shft = false;
			}).bind('blur',function (e) { // workaround for Opera -- http://www.webdeveloper.com/forum/showthread.php?p=806588
				if (9 == pressed.last) $(e.target).one('focus',function (e) {pressed.last = null;}).get(0).focus();
			});
		
		});
	};
	
	// define and expose any extra methods
	$.fn.tabby.catch_kc = function(e) { return e.keyCode ? e.keyCode : e.charCode ? e.charCode : e.which; };
	$.fn.tabby.pressed = {shft : false, ctrl : false, alt : false, last: null};
	
	// private function for debugging
	function debug($obj) {
		if (window.console && window.console.log)
		window.console.log('textarea count: ' + $obj.size());
	};

	function process_keypress (o,shft,options) {
		var scrollTo = o.scrollTop;
		//var tabString = String.fromCharCode(9);
		
		// gecko; o.setSelectionRange is only available when the text box has focus
		if (o.setSelectionRange) gecko_tab (o, shft, options);
		
		// ie; document.selection is always available
		else if (document.selection) ie_tab (o, shft, options);
		
		o.scrollTop = scrollTo;
	}
	
	// plugin defaults
	$.fn.tabby.defaults = {tabString : String.fromCharCode(9)};
	
	function gecko_tab (o, shft, options) {
		var ss = o.selectionStart;
		var es = o.selectionEnd;	
				
		// when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control
		if(ss == es) {
			// SHIFT+TAB
			if (shft) {
				// check to the left of the caret first
				if ("\t" == o.value.substring(ss-options.tabString.length, ss)) {
					o.value = o.value.substring(0, ss-options.tabString.length) + o.value.substring(ss); // put it back together omitting one character to the left
					o.focus();
					o.setSelectionRange(ss - options.tabString.length, ss - options.tabString.length);
				} 
				// then check to the right of the caret
				else if ("\t" == o.value.substring(ss, ss + options.tabString.length)) {
					o.value = o.value.substring(0, ss) + o.value.substring(ss + options.tabString.length); // put it back together omitting one character to the right
					o.focus();
					o.setSelectionRange(ss,ss);
				}
			}
			// TAB
			else {			
				o.value = o.value.substring(0, ss) + options.tabString + o.value.substring(ss);
				o.focus();
	    		o.setSelectionRange(ss + options.tabString.length, ss + options.tabString.length);
			}
		} 
		// selections will always add/remove tabs from the start of the line
		else {
			// split the textarea up into lines and figure out which lines are included in the selection
			var lines = o.value.split("\n");
			var indices = new Array();
			var sl = 0; // start of the line
			var el = 0; // end of the line
			var sel = false;
			for (var i in lines) {
				el = sl + lines[i].length;
				indices.push({start: sl, end: el, selected: (sl <= ss && el > ss) || (el >= es && sl < es) || (sl > ss && el < es)});
				sl = el + 1;// for "\n"
			}
			
			// walk through the array of lines (indices) and add tabs where appropriate						
			var modifier = 0;
			for (var i in indices) {
				if (indices[i].selected) {
					var pos = indices[i].start + modifier; // adjust for tabs already inserted/removed
					// SHIFT+TAB
					if (shft && options.tabString == o.value.substring(pos,pos+options.tabString.length)) { // only SHIFT+TAB if there's a tab at the start of the line
						o.value = o.value.substring(0,pos) + o.value.substring(pos + options.tabString.length); // omit the tabstring to the right
						modifier -= options.tabString.length;
					}
					// TAB
					else if (!shft) {
						o.value = o.value.substring(0,pos) + options.tabString + o.value.substring(pos); // insert the tabstring
						modifier += options.tabString.length;
					}
				}
			}
			o.focus();
			var ns = ss + ((modifier > 0) ? options.tabString.length : (modifier < 0) ? -options.tabString.length : 0);
			var ne = es + modifier;
			o.setSelectionRange(ns,ne);
		}
	}
	
	function ie_tab (o, shft, options) {
		var range = document.selection.createRange();
		
		if (o == range.parentElement()) {
			// when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control
			if ('' == range.text) {
				// SHIFT+TAB
				if (shft) {
					var bookmark = range.getBookmark();
					//first try to the left by moving opening up our empty range to the left
				    range.moveStart('character', -options.tabString.length);
				    if (options.tabString == range.text) {
				    	range.text = '';
				    } else {
				    	// if that didn't work then reset the range and try opening it to the right
				    	range.moveToBookmark(bookmark);
				    	range.moveEnd('character', options.tabString.length);
				    	if (options.tabString == range.text) 
				    		range.text = '';
				    }
				    // move the pointer to the start of them empty range and select it
				    range.collapse(true);
					range.select();
				}
				
				else {
					// very simple here. just insert the tab into the range and put the pointer at the end
					range.text = options.tabString; 
					range.collapse(false);
					range.select();
				}
			}
			// selections will always add/remove tabs from the start of the line
			else {
			
				var selection_text = range.text;
				var selection_len = selection_text.length;
				var selection_arr = selection_text.split("\r\n");
				
				var before_range = document.body.createTextRange();
				before_range.moveToElementText(o);
				before_range.setEndPoint("EndToStart", range);
				var before_text = before_range.text;
				var before_arr = before_text.split("\r\n");
				var before_len = before_text.length; // - before_arr.length + 1;
				
				var after_range = document.body.createTextRange();
				after_range.moveToElementText(o);
				after_range.setEndPoint("StartToEnd", range);
				var after_text = after_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n
				
				var end_range = document.body.createTextRange();
				end_range.moveToElementText(o);
				end_range.setEndPoint("StartToEnd", before_range);
				var end_text = end_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n
								
				var check_html = $(o).html();
				$("#r3").text(before_len + " + " + selection_len + " + " + after_text.length + " = " + check_html.length);				
				if((before_len + end_text.length) < check_html.length) {
					before_arr.push("");
					before_len += 2; // for the \r\n that was trimmed	
					if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length))
						selection_arr[0] = selection_arr[0].substring(options.tabString.length);
					else if (!shft) selection_arr[0] = options.tabString + selection_arr[0];	
				} else {
					if (shft && options.tabString == before_arr[before_arr.length-1].substring(0,options.tabString.length)) 
						before_arr[before_arr.length-1] = before_arr[before_arr.length-1].substring(options.tabString.length);
					else if (!shft) before_arr[before_arr.length-1] = options.tabString + before_arr[before_arr.length-1];
				}
				
				for (var i = 1; i < selection_arr.length; i++) {
					if (shft && options.tabString == selection_arr[i].substring(0,options.tabString.length))
						selection_arr[i] = selection_arr[i].substring(options.tabString.length);
					else if (!shft) selection_arr[i] = options.tabString + selection_arr[i];
				}
				
				if (1 == before_arr.length && 0 == before_len) {
					if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length))
						selection_arr[0] = selection_arr[0].substring(options.tabString.length);
					else if (!shft) selection_arr[0] = options.tabString + selection_arr[0];
				}

				if ((before_len + selection_len + after_text.length) < check_html.length) {
					selection_arr.push("");
					selection_len += 2; // for the \r\n that was trimmed
				}
				
				before_range.text = before_arr.join("\r\n");
				range.text = selection_arr.join("\r\n");
				
				var new_range = document.body.createTextRange();
				new_range.moveToElementText(o);
				
				if (0 < before_len)	new_range.setEndPoint("StartToEnd", before_range);
				else new_range.setEndPoint("StartToStart", before_range);
				new_range.setEndPoint("EndToEnd", range);
				
				new_range.select();
				
			} 
		}
	}

// end of closure
})(jQuery);

$(
	function () {	
		$( "textarea.use-tabs-insertion" ).tabby();
	}
)

