    var scroller = function( config ) {
      var self = this;
      var disableSelection   = false;

      self.isVertical        = true;
      self.handleScrollDown  = false;
      self.handleScrollUp    = false;
      self.handleSliderMove  = false;
      self.updater           = false;

      self.sliderData = {
        mouseBaseTop: 0,
        baseTop:      0,
        lastStep:     0,
        size:         0
      };

      this.getContentSize = function() {
        return self.config.contentSize || ( self.isVertical ? self.contentZone.scrollHeight : self.contentZone.scrollWidth );
      }

      this.getMargin = function( element ) {
        margin = element.style[self.marginType] || '0px';
        return parseInt( margin.replace('px', '') );
      }

      this.setMargin = function( element, margin ) {
        if ( margin < 0 ) {
          margin = 0;
        }
        element.style[self.marginType] = margin + 'px';
      }

      this.scrollDown = function( pixel, noforce ) {
        if ( !self.handleScrollDown && noforce ) return;
        newScrollPos = self.contentZone[self.scrollType] + pixel;
        self.contentZone[self.scrollType] = ( newScrollPos > self.contentRange ) ? self.contentRange : newScrollPos;
        self.updateSlider();
      }

      this.scrollUp = function( pixel, noforce ) {
        if ( !self.handleScrollUp && noforce ) return;
        self.scrollDown( -pixel );
      }

      this.updateSlider = function() {
        if ( self.contentRange == 0 ) {
          return false;
        }
        contentScroll = self.contentZone[self.scrollType];
        percent       = contentScroll / self.contentRange;
        self.setMargin( self.slider, ( self.scrollRange ) * percent );
      }

      this.scrollBySlider = function() {
        sliderMargin = self.getMargin( self.slider );
        percent      = sliderMargin / self.scrollRange;

        //console.log( self.scrollRange, sliderMargin, percent );
        self.contentZone[self.scrollType] = Math.round( this.contentRange * percent );
        //self.setMargin( self.contentZone, -sliderMargin * self.ratio );
      }

      this.handleWheel = function( event ) {
        delta = Event.wheel( event );
        //if ( delta > 0 ) {
          step = -self.config.step || -25;
          //self.scrollUp( delta * step );
        //} else if ( delta <= 0 ) {
          //step = -self.config.step || -25;
          //self.scrollDown( delta * step );
        //}
        //console.log( self.contentZone[self.scrollType] + ( step * delta ), step );
        if ( self.scrolling ) {
          event.stop();
          return false;
        }
        if ( !self._scrollTo ) {
          self._scrollTo = self.contentZone[self.scrollType];
        }
        //console.log( self._scrollTo);
        self._scrollTo += ( step * delta );
        //console.log( self._scrollTo, step, delta );
        clearTimeout( self.timer );
        self.timer = setTimeout( function(){
          //console.log( 'scrolllll now!', self._scrollTo );
          self.scrollTo( self._scrollTo, true );
          self._scrollTo = 0;
          //console.log( self,'clear:', self._scrollTo, step, delta );
        }, 100 );

        event.stop();
        return false;

      }

      this.updateContentSize = function( size, noScrollUp ) {
        if ( !size ) {
          size = self.getContentSize();//self.contentZone[self.isVertical ? 'scrollHeight' : 'scrollWidth'];
        }

        self.contentMaxSize  = size;
        self.contentRange    = self.contentMaxSize - self.contentSize;
        _sliderSize = ( self.contentSize / self.contentMaxSize ) * self.sliderZoneSize;

        if ( _sliderSize < 15 ) {
          _sliderSize = 15;
        }
        self.sliderSize       = _sliderSize;
        self.scrollRange      = self.sliderZoneSize - self.sliderSize;
        self.sliderData.size  = Math.round( _sliderSize );
        var _sizeType = self.isVertical ? 'height' : 'width';
        self.slider.style[_sizeType] = Math.round( _sliderSize ) + 'px';

        _newSliderEnd = self.getMargin( self.slider ) + _sliderSize;

        if ( _newSliderEnd >= self.sliderZoneSize ) {
          self.setMargin( self.slider, self.sliderZoneSize - _sliderSize );
          self.scrollBySlider();
        }
        if ( self.scrollRange < 5 ) {
          self.container.hide();
        } else {
          self.container.show();
        }

        if ( !noScrollUp ) {
          self.scrollTo( 0 );
        } else {
          //self.scrollUp( 0 );
        }

      }

      this.scrollTo = function( target, updateSlider, force ) {
        var current   = self.contentZone[self.scrollType];
        if ( target > self.contentRange ) {
          target = self.contentRange;
        }
        //var step      = ( target - current ) / ( self.config.fps || 10 );
        var step = 0;
        if ( !self.config.effect ) {
          self.contentZone[self.scrollType] = target;
          if ( updateSlider ) {
            self.updateSlider();
          }
          return ;
        }

        if ( self.config.effect == 'sin' ) {
          if ( self.scrolling && !force ) {
            return;
          } else if( force ) {
            clearInterval( self.timer );
          }

          self.scrolling = true;
          self.timer = setInterval( function() {

            if ( step > ( self.config.fps || 10 ) ) {
              clearTimeout( self.timer );
              self.scrolling = false;
              newPos = target;
            } else {
              percent = step / ( self.config.fps || 10 );
              newPos = current + Math.round( Math.sin( ( Math.PI / 2 ) * percent ) * ( target - current ) );
              //console.log( newPos );
            }
            self.contentZone[self.scrollType] = newPos;
            if ( updateSlider ) {
              self.updateSlider();
            }
            step++;
          }, 700 / ( self.config.fps || 10 ) );
        }
      }

      var init = function() {
        self.config = config;

        self.container   = $( config.scrollbar );
        self.up          = self.container.down('div.up');
        self.down        = self.container.down('div.down');
        self.slider      = self.container.down('div.slider');
        self.sliderZone  = self.container.down('div.area');
        self.scrollTimer = false;

        self.contentZone = $( config.content );

        _szs = self.sliderZone.getDimensions();
        self.isVertical          = _szs.width < _szs.height;

        self.sliderZoneSize      = self.isVertical ? _szs.height : _szs.width;

/*        if ( !config.contentSize ) {
          self.contentMaxSize  = self.isVertical ? self.contentZone.scrollHeight : self.contentZone.scrollWidth // = self.isVertical ? self.contentZone.offsetHeight : self.contentZone.offsetWidth;
        } else {
          self.contentMaxSize = config.contentSize;
        }
*/
        if ( Object.isFunction( config.contentSize ) ) {
          self.getContentSize = config.contentSize;
        } /*else if ( config.contentSize ) {
          self.contentMaxSize = self.contentSize;
        } */else {
          self.contentMaxSize  = self.getContentSize();
        }

        self.contentSize     = self.isVertical ? self.contentZone.getHeight() : self.contentZone.getWidth();

        self.sliderSize      = self.isVertical ? self.slider.offsetHeight : self.slider.offsetWidth;

        self.ratio         = ( self.sliderZoneSize - self.sliderSize ) / ( self.isVertical ? self.contentZone.offsetHeight : self.contentZone.offsetWidth - self.contentSize );

        self.scrollRange   = self.sliderZoneSize - self.sliderSize;

        self.contentRange  = self.getContentSize() - self.contentSize;

        //console.log( self.scrollRange, self.contentRange );

        //console.log( self.ratio, self.sliderZoneSize, self.contentSize, self.isVertical ? self.contentZone.offsetHeight : self.contentZone.offsetWidth );

        self.marginType    = self.isVertical ? 'top' : 'left';
        self.scrollType    = self.isVertical ? 'scrollTop' : 'scrollLeft';

        self.updateSlider();

        if ( self.isVertical ) {
          self.contentZone.observe( 'mousewheel',     self.handleWheel, false );
          self.contentZone.observe( 'DOMMouseScroll', self.handleWheel, false );
        }

        if ( self.slider ) {
          self.slider.observe( 'mousedown', function( event ) {
            disableSelection = true;
            document.body.style.MozUserSelect = 'none';
            self.handleSliderMove        = true;
            self.sliderData.mouseBaseTop = self.isVertical ? event.pointerY() : event.pointerX();
            self.sliderData.baseTop      = self.getMargin( self.slider );

          });
        }

        self.down.observe( 'mousedown', function( event ) {
          //self.scrollDown( 10, true );
          disableSelection = true;
          //document.body.style.MozUserSelect = 'none';
          //self.timer = setInterval( function(){ self.scrollDown( 5, true ) }, 100 );
          if ( !self.config.effect ) {
            self.scrollDown( 10, true );
            self.timer = setInterval( function(){ self.scrollDown( 5, true ) }, 100 );
          } else {
            self.scrollTo( self.contentZone[self.scrollType] + ( ( self.config.step || 30 ) * 2 ), true, true );
            //self.timer = setInterval( function(){ self.scrollTo( self.contentZone[self.scrollType] + ( self.config.step || 30 ), true, true ) }, 100 );
          }
        });

        self.down.observe( 'mouseover', function( event ) {
          self.handleScrollDown = true;
        });

        self.down.observe( 'mouseout', function( event ) {
          self.handleScrollDown = false;
        });

        self.up.observe( 'mousedown', function( event ) {
          disableSelection = true;
          document.body.style.MozUserSelect = 'none';
          if ( !self.config.effect ) {
            self.scrollUp( 10, true );
            self.timer = setInterval( function(){ self.scrollUp( 5, true ) }, 100 );
          } else {
            self.scrollTo( self.contentZone[self.scrollType] - ( ( self.config.step || 30 ) * 2 ), true, true );
            //self.timer = setInterval( function(){ console.log('FEL'); self.scrollTo( self.contentZone[self.scrollType] - ( self.config.step || 30 ), true, true ); }, 100 );
          }
        });

        self.up.observe( 'mouseover', function( event ) {
          self.handleScrollUp = true;
        });

        self.up.observe( 'mouseout', function( event ) {
          self.handleScrollUp = false;
        });

        self.sliderZone.observe('mousedown', function( event ) {
          disableSelection = true;
          document.body.style.MozUserSelect = 'none';
          //_slider   = self.isVertical ? self.slider.cumulativeOffset().top : self.slider.cumulativeOffset().left;
          _slider   = self.isVertical ? self.slider.cumulativeOffset().top : self.slider.cumulativeOffset().left;
          _pointer  = self.isVertical ? event.pointerY() : event.pointerX();
          //alert( self.contentZone.offsetHeight );//self.contentZone.offsetHeight );
          if ( _slider + self.sliderData.size < _pointer ) {
            if ( !self.config.step ) {
              self.scrollDown( 25 );
            } else {
              self.scrollTo( self.contentZone[self.scrollType] + ( self.config.step * 3 || 30 ), true );
            }

          } else if( _pointer < _slider ) {
            if ( !self.config.step ) {
              self.scrollUp( 25 );
            } else {
              self.scrollTo( self.contentZone[self.scrollType] - ( self.config.step * 3 || 30 ), true );
            }
          }
        });

        document.observe( 'mouseup', function( event ) {
          if ( !self.scrolling ) {
            clearInterval( self.timer );
          }

          document.body.style.MozUserSelect = '';
          self.handleSliderMove = false;
          disableSelection = false;
        }); // end observe mouseup

        document.body.onselectstart = function( event ) {
          if ( disableSelection ) return false;
          else return true;
        }

        if ( Prototype.Browser.Gecko ) {
          self.sliderZone.style.MozUserSelect = "none";
        }

        document.observe( 'mousemove', function( event ) {
          // disable selection
          //event.element().style.MozUserSelect = ( Prototype.Browser.Gecko && ( self.handleSliderMove || self.handleScrollUp || self.handleScrollDown ) ) ? "none" : "";

          if ( self.handleSliderMove ) {
            mouseMovedBy = self.sliderData.mouseBaseTop - ( self.isVertical ? event.pointerY() : event.pointerX() );
            newpos = self.sliderData.baseTop - mouseMovedBy;
            scrollStep = ( self.scrollRange / self.contentRange ) * ( self.config.step || 1 );
            //console.log( mouseMovedBy, newpos, self.scrollRange );

            if ( newpos >= 0 && newpos <= self.scrollRange ) {
              self.setMargin( self.slider, newpos );
              if ( self.config.step && Math.round( newpos / scrollStep ) == self.sliderData.lastStep ) {
                //console.log( Math.round( newpos / scrollStep ), newpos, scrollStep );
                return;
              } else if( self.config.step ) {
                //self.contentZone[self.scrollType] = Math.round( Math.floor( newpos / scrollStep ) * self.config.step );
                clearTimeout( self.scrollTimer );
                self.scrollTimer = setTimeout( function(){ self.scrollTo( Math.round( Math.round( newpos / scrollStep ) * self.config.step ), false, true );}, 100 );
                self.sliderData.lastStep = Math.round( newpos / scrollStep );
                //self.updateSlider();
              } else {
                self.scrollBySlider();
                //self.slider[self.scrollType] = newpos;
              }
            } // end if pos ...
          } // end if handleSliderMove
        }); // end document.observe mousemove
        self.updater = setInterval( function() { self.updateContentSize( 0, true ); }, 1000 );
      }(); // end constructor
    }
