function Scroller( element ) 
{
	this.id = Scroller.Instances.length;
	Scroller.Instances[ this.id ] = this;


	// ----------------------------------------------------------------------------------------------------
	// ПЕРЕМЕННЫЕ
	// ----------------------------------------------------------------------------------------------------

	// элементы системы скроллирования
	Scroller.prototype.element;					// контейнер, содержащий всю систему
	Scroller.prototype.contentContainer;		// контейнер, ограничивающий размеры контента
	Scroller.prototype.contentBlock;				// контейнер, содержащий контент
	Scroller.prototype.sliderPathVertical;		// область перемещения вертикального слайнер
	Scroller.prototype.sliderVertical;			// вертикальный слайдер
	Scroller.prototype.scrollTopControl;		// верхний контрол прокрутки
	Scroller.prototype.scrollBottomControl;	// нижний контрол прокрутки

	// настройка параметров скроллирования
	Scroller.prototype.interval = 0;
	Scroller.prototype.scrollDelay = 1;
	Scroller.prototype.scrollStep = 2;

	// габариты и координаты элементов системы
	Scroller.prototype.sliderStartY;
	Scroller.prototype.sliderStartTop;
	Scroller.prototype.sliderHeight;
	Scroller.prototype.sliderPathHeight;
	Scroller.prototype.scrolledSliderPath;
	Scroller.prototype.scrolledContentHeight;
	Scroller.prototype.contentMarginTop;
	Scroller.prototype.contentMarginBottom;
	Scroller.prototype.mouseWheelTimeout;

	// [construct] > конструктор системы скроллирования
	// инициализация элементов системы
	// формирование необходимой DOM струкуры 
	// формирование системы обработки событий
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.construct = function() {
		this.element 				= element;
		this.contentContainer		= document.createElement( 'div' );
		this.contentBlock 			= document.createElement( 'div' );
		this.sliderPathVertical		= document.createElement( 'div' );
		this.sliderVertical			= document.createElement( 'span' );
		this.scrollTopControl 		= document.createElement( 'div' );
		this.scrollBottomControl 	= document.createElement( 'div' );

		// формирование струкуры системы
		while( this.element.childNodes.length )
			this.contentBlock.insertBefore( this.element.childNodes[0], null );

		this.element.appendChild( this.contentContainer );
		this.contentContainer.appendChild( this.contentBlock );
		this.element.appendChild( this.sliderPathVertical ); 
		this.sliderPathVertical.appendChild( this.sliderVertical );
		this.element.appendChild( this.scrollTopControl );
		this.element.appendChild( this.scrollBottomControl );

		// простановка необходимых классов
		$( this.element ).removeClass( 'scrolledBlock' );
		$( this.element ).addClass( 'scrollContainer' );
		$( this.contentContainer ).addClass( 'scrollContentContainer' );
		$( this.contentBlock ).addClass( 'scrollContent' );
		$( this.sliderPathVertical ).addClass( 'scrollSliderPathVertical' );
		$( this.sliderVertical ).addClass( 'scrollSliderVertical' );
		$( this.scrollTopControl ).addClass( 'scrollTopControl' );
		$( this.scrollBottomControl ).addClass( 'scrollBottomControl' );

		// простановка необходимых обработчиков событий
		$( this.sliderVertical ).bind( 'mousedown', this.startSliderMoving );

		$( this.scrollTopControl ).bind( 'mouseover', this.startScrollingUp );
		$( this.scrollTopControl ).bind( 'mouseout', this.stopScrollingUp );

		$( this.scrollBottomControl ).bind( 'mouseover', this.startScrollingDown );
		$( this.scrollBottomControl	).bind( 'mouseout', this.stopScrollingDown );

		$( this.element ).bind( 'mouseover', this.initMouseWheel );
		$( window ).bind( 'resize', Scroller.recalculateDimension );

		// предрасчёт размеров			
		this.scrolledContentHeight 	= this.contentBlock.clientHeight - this.contentContainer.clientHeight;
		// элементы управления скроллером отображаются только, если контент не помещается в контейнер
		if ( this.scrolledContentHeight > 0 )
			this.scrolledSliderHeight 	= this.sliderPathVertical.clientHeight - this.sliderVertical.clientHeight;
		else {
			this.sliderPathVertical.style.display = 'none';
			this.scrollTopControl.style.display = 'none';
			this.scrollBottomControl.style.display = 'none';
		}
	}
	
	// ----------------------------------------------------------------------------------------------------
	// ОБРАБОТКА СОБЫТИЙ ПРЕМЕЩЕНИЯ СЛАЙДЕРА
	// ----------------------------------------------------------------------------------------------------
	
	// [startSliderMoving] > обработка события [onmousedown] начала перетаскивания слайдера
	// добавление обработчиков перемещения слайдеров
	// удаления обработчиков управления контролами
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.startSliderMoving = function( e ) {
		var instance = Scroller.getCurrentInstance( e, 'sliderVertical' );
		
		if ( instance.contentContainer.clientHeight < instance.contentBlock.clientHeight )
		{
			Scroller.ActiveSlideredInstance = instance;
			instance.sliderIsMoving = true;
			instance.sliderStartY = e.clientY;
			instance.sliderStartTop = parseInt( instance.sliderVertical.style.top + 0 );
   
			// перемещение слайдера обрабатывается на фоне всего документа
			$( document ).bind( 'mousemove', 	instance.sliderMoving );
			$( document ).bind( 'mouseup',		instance.stopSliderMoving );
		}
	}
	
	// [sliderMoving] > обработка обытия [onmousemove] перемещения слайдера
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.sliderMoving = function( e ) {
		var instance = Scroller.ActiveSlideredInstance;
		var newSliderTop = e.clientY - instance.sliderStartY + instance.sliderStartTop;
		
		if ( newSliderTop < 0 )
		{
			instance.sliderVertical.style.top = 0 + 'px';
			instance.contentBlock.style.top = 0 + 'px';
		}
		else if ( newSliderTop > instance.scrolledSliderHeight )
		{
			instance.sliderVertical.style.top = instance.scrolledSliderHeight + 'px';
			instance.contentBlock.style.top = (-1)*( instance.scrolledContentHeight ) + 'px';
		}
		else
		{			
			instance.sliderVertical.style.top = newSliderTop + "px";
			instance.contentBlock.style.top = (-1)*( newSliderTop )*( instance.scrolledContentHeight )/( instance.scrolledSliderHeight ) + 'px';
		}
	}
	
	// [stopSliderMoving] > обработка события [onmouseup] прекращения перемещения слайдера
	// удаление обработчиков событий перемещения слайдера
	// возвращение обработчиков событий управления контролами
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.stopSliderMoving = function() {
		var instance = Scroller.ActiveSlideredInstance;
		$( document ).unbind( 'mousemove', 	instance.sliderMoving );
		$( document ).unbind( 'mouseup',	instance.stopSliderMoving );
		Scroller.ActiveSlideredInstance = undefined;
	}
	
	// ----------------------------------------------------------------------------------------------------
	// ОБРАБОТКА СОБЫТИЙ УПРАВЛЕНИЯ КОНТРОЛАМИ
	// ----------------------------------------------------------------------------------------------------
	
	// [setSliderPosition] > изменение позиции слайдера в зависимости
	// от позиции передвинутого контролами контента
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.setSliderPosition = function( factor ) {
		var sliderTop = ( this.scrolledSliderHeight ) * factor;
		if ( sliderTop < 0 )
			this.sliderVertical.style.top = 0 + 'px';
		else if ( sliderTop > this.sliderPathHeight - this.sliderHeight )
			this.sliderVertical.style.top = this.sliderPathHeight - this.sliderHeight + 'px';
		else
			this.sliderVertical.style.top = sliderTop + 'px';
	}
	
	// [scrollContentUp] > скроллироваие содержимого вверх на [step] пикселей
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.scrollContentUp = function( step ) {
		if ( step == undefined ) step = this.scrollStep;
		if ( this.contentBlock.offsetTop < 0 ) {
			if ( this.contentBlock.offsetTop + step < 0 )
				this.contentBlock.style.top = ( parseInt( this.contentBlock.style.top + 0 ) + step ) + 'px';
			else
				this.contentBlock.style.top = 0 + 'px';
			this.setSliderPosition( ( ( this.contentBlock.offsetTop ) * (-1) ) / ( this.scrolledContentHeight ) );
		}
	}
	
	// [scrollContentDown] > скроллироваие содержимого вниз на [step] пикселей
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.scrollContentDown = function( step ) {
		if ( step == undefined ) step = this.scrollStep;
		if ( this.scrolledContentHeight + this.contentBlock.offsetTop > 0 ) {
				if ( this.scrolledContentHeight + this.contentBlock.offsetTop - step < 0 )
					this.contentBlock.style.top = ( -this.scrolledContentHeight ) + 'px';
				else
					this.contentBlock.style.top = ( parseInt( this.contentBlock.style.top + 0 ) - step ) + 'px';
			this.setSliderPosition( ( this.contentBlock.offsetTop * (-1) ) / ( this.scrolledContentHeight ) );
		}
	}
	
	// [startScrollingContentUp] > обработка события [onmouseover] для контрола перемещения вверх
	// установка интервала и изменение классов
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.startScrollingUp = function( e ) {
		var instance = Scroller.getCurrentInstance( e, 'scrollTopControl' );

		$( instance.scrollTopControl ).removeClass( 'scrollTopControl' ).addClass( 'scrollTopControlSelected' );

		instance.interval = setInterval( 'Scroller.Instances[' + instance.id + '].scrollContentUp()', instance.scrollDelay );
	}
	
	// [startScrollingContentDown] > обработка события [onmouseover] для контрола перемещения вниз
	// установка интервала и изменение классов
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.startScrollingDown = function( e ) {
		var instance = Scroller.getCurrentInstance( e, 'scrollBottomControl' );

		$( instance.scrollBottomControl ).removeClass( 'scrollBottomControl' ).addClass( 'scrollBottomControlSelected' );

		instance.interval = setInterval( 'Scroller.Instances[' + instance.id + '].scrollContentDown()', instance.scrollDelay );
	}
	
	// [stopScrollingContentUp] > обработка события [onmouseout] для контрола перемещения вверх
	// очистка интервала и изменение классов
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.stopScrollingUp = function( e ) {
		var instance = Scroller.getCurrentInstance( e, 'scrollTopControl' );
				
		clearInterval( instance.interval );
		
		$( instance.scrollTopControl ).removeClass( 'scrollTopControlSelected' ).addClass( 'scrollTopControl' );
	}
	
	// [stopScrollingContentDown] > обработка события [onmouseout] для контрола перемещения вниз
	// очистка интервала и изменение классов
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.stopScrollingDown = function( e ) {
		var instance = Scroller.getCurrentInstance( e, 'scrollBottomControl' );
				
		clearInterval( instance.interval );
		
		$( instance.scrollBottomControl ).removeClass( 'scrollBottomControlSelected' ).addClass( 'scrollBottomControl' );
	}
	
	// [initMouseWheel] > обработка события [onmouseover] для системы скроллинга
	// добавление обработчика вращения колеса мышки
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.initMouseWheel = function( e ) {
		var instance;
		
		if ( undefined != ( instance = Scroller.ActiveWheeledInstance ) ) {
			clearTimeout( instance.mouseWheelTimeout );	
		}
		else if ( instance = Scroller.getCurrentInstanceRecursive( e, 'element' ) )
		{
			if ( window.addEventListener )
				/* DOMMouseScroll для mozilla. */
				window.addEventListener( 'DOMMouseScroll', instance.processMouseWheel, false );
			else
				/* IE/Opera. */
				window.onmousewheel = document.onmousewheel = instance.processMouseWheel;
   
   			Scroller.ActiveWheeledInstance = instance;
			$( instance.element ).bind( 'mouseout', 	instance.checkMouseWheelHandler );
		}
	}
	
	// [checkMouseWheelHandler] > проверка действительно ли мышка больше не в области прокрутка
	// или она просто заскочила на один из дочерних элементов
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.checkMouseWheelHandler = function( e ) {
		var instance = Scroller.ActiveWheeledInstance;
		instance.mouseWheelTimeout = setTimeout( 'Scroller.Instances[' + instance.id + '].removeMouseWheelHandler()', 0 );
	}
	
	// [removeMouseWheelHandler] > обрадотчик прокрутки колеса снимается,
	// когда мышка покидает область скроллинга
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.removeMouseWheelHandler = function( e ) {
		var instance = Scroller.ActiveWheeledInstance;

		if ( window.addEventListener )
			/* DOMMouseScroll is for mozilla. */
			window.removeEventListener( 'DOMMouseScroll', instance.processMouseWheel, false );
		else
			/* IE/Opera. */
			window.onmousewheel = document.onmousewheel = null;
		
		$( instance.element ).unbind( 'mouseout', 	instance.checkMouseWheelHandler );
		Scroller.ActiveWheeledInstance = undefined;
	}
	
	// [processMouseWheel] > прокрутка контента в зависимости от вращение колеса мышки
	// ----------------------------------------------------------------------------------------------------
	Scroller.prototype.processMouseWheel = function( e ) {
		var instance = Scroller.ActiveWheeledInstance;
		var delta = 0;
		
		/*IE*/
		if (!e) e = window.event;
		/* IE/Opera */
		if ( e.wheelDelta ) {
				delta = e.wheelDelta/120;
				/* В Opera 9, delta отличается знаком от delta IE */
				if (window.opera)
						delta = -delta;
		}
		/* Mozilla */
		else if (e.detail) {
				/* В Mozilla, знак delta в 3 раза больше отличается от delta IE */
				delta = -e.detail/3;
		}
		/* Обработка ненулевой delta
		 * Теперь, если delta положительна колесо было прокручено вверх, отрицательна - вниз */
		if ( delta > 0 )
			instance.scrollContentUp( instance.scrollStep*delta*10 );
		else if ( delta < 0 )
			instance.scrollContentDown( -instance.scrollStep*delta*10 );

		/* Отключение обработки колеса по умолчанию, где это возможно */
		if ( e.preventDefault )
				e.preventDefault();
		e.returnValue = false;
	}
}

// массив с указателями на все экземпляры класса
Scroller.Instances = new Array();
// экземпляр класса, актиивный в данный момент
Scroller.ActiveSlideredInstance = undefined;
Scroller.ActiveWheeledInstance = undefined;

// [Init] > Инициализация всех скроллируемых блоков на странице
// также удаление обработчиков прокрутки колёсика мышки для существующих блоков
// обнуление структуры, содержащей информацию о существующих блоках
// ----------------------------------------------------------------------------------------------------
Scroller.Init = function() {
	
	// удаление глобальных обработчиков для всех екземляров скроллера
	for ( var i = 0; i < Scroller.Instances.length; i++ ) {
		var instance = Scroller.Instances[ i ];
		$( instance.element ).unbind( 'mouseover', 	instance.initMouseWheel );
		if ( Scroller.ActiveWheeledInstance == instance )
			instance.removeMouseWheelHandler();	
		instance = null;
	}
	Scroller.Instances = null;
	Scroller.Instances = new Array();
	
	Scroller.ActiveSlideredInstance = undefined;
	Scroller.ActiveWheeledInstance = undefined;
	
	$( 'div.scrolledBlock' ).each( function(){ ( new Scroller( this ) ).construct(); } );
}

// [getCurrentInstance] > выяснение событие какого экземляра класса было спровоцировано
// ( разрещение экземпляра класса по [e.target] )
// ----------------------------------------------------------------------------------------------------
Scroller.getCurrentInstance = function( e, elementName ) {
	for ( var i = 0; i < Scroller.Instances.length; i++ )
	{
		if ( getEventSrcElement(e) == Scroller.Instances[i][elementName] )
			return Scroller.Instances[i];
	}
}

// [getCurrentInstanceRecursive] > выяснение событие какого экземляра класса было спровоцировано
// ( разрещение экземпляра класса по [e.target], а также предкам [e.target] до [elementName] )
// ----------------------------------------------------------------------------------------------------
Scroller.getCurrentInstanceRecursive = function( e, elementName ) {
	
	for ( var i = 0; i < Scroller.Instances.length; i++ )
	{
		var target = getEventSrcElement(e);
		do {
			if ( target == Scroller.Instances[i][elementName] )
				return Scroller.Instances[i];
		} while ( !$( target ).hasClass( 'scrollContainer' ) && (target = target.parentNode) );
	}
}

// [recalculateDimension] > пересчёт размеров контента при изменении размеров окна
// ----------------------------------------------------------------------------------------------------
Scroller.recalculateDimension = function() {
	var instance;
	for ( var i = 0; i < Scroller.Instances.length; i++ ){
		instance = Scroller.Instances[i];
		instance.scrolledContentHeight = (instance.contentBlock.clientHeight - instance.contentContainer.clientHeight);
		
		// если нужен скролинг
		if ( instance.scrolledContentHeight > 0 )
		{
			// если ранее элементы были скрыты и нужен скролинг, то теперь их нужно отобразить
			if ( instance.sliderPathVertical.style.display == 'none' )
			{
				instance.sliderPathVertical.style.display = 'block';
				instance.scrollTopControl.style.display = 'block';
				instance.scrollBottomControl.style.display = 'block';
			}
			
			if ( instance.contentBlock.offsetTop <= 0 )
				instance.contentBlock.style.top = 0 + 'px';
			else if ( instance.scrolledContentHeight + instance.contentBlock.offsetTop < 0 )
				instance.contentBlock.style.top = ( -instance.scrolledContentHeight ) + 'px';

			instance.setSliderPosition( ( instance.contentBlock.offsetTop * (-1) ) / ( instance.scrolledContentHeight ) );
		}
		// если скролинг не нужен - элементы скролинга скрываются, контент перемещается вверх
		else
		{
			instance.contentBlock.style.top = 0 + 'px';
			instance.sliderPathVertical.style.display = 'none';
			instance.scrollTopControl.style.display = 'none';
			instance.scrollBottomControl.style.display = 'none';
		}
	}
}
$( function(){ Scroller.Init() });

