function Slider(wrapper, params) {
    if (!(wrapper instanceof jQuery))
        return;

    this.defaults = {
        //The active element when the slider is first loaded
        start: null,
        //The class that identifies slider elements
        elementClass: 'slider-element',
        //The class that identifies the slider navigation wrapper
        navigationClass: 'slider-nav',
        //The class that identifies a navigation number element
        navigationElementClass: 'slider-nav-number',
        //Specifies whether or not to automatically slide to a new element
        autoSlide: false,
        //Specifies the time in milliseconds between each auto-slide
        autoSlideInterval: 2000,
        //Specifies whether or not to activate the slider immediately after it's been loaded
        autoActivate: true,
        //The four offset variables below specify the offset for the absolute position of an element that is about to be loaded
        topOffset: 0,
        rightOffset: 0,
        bottomOffset: 0,
        leftOffset: 0,
        //The speed at which each slider element will fade out
        fadeSpeed: 'slow',
        //Specify thumbnail images to use instead of the usual number-based navigation.
        //If this is not an array of image URLs or a jQuery object collection with the same size as the slider elements in the slider, the default numbering navigation will be used.
        navigationThumbnails: null,
        //The maximum amount of numbers to be displayed in the navigation bar (excluding the next and prev elements)
        maxNavigationLength: 9,
        //Specifies whether or not to automatically create a navigation bar when the slider is activated
        createNavigation: true,
        //Specifies whether or not to reload the entire navigation bar when the slider slides to a new element
        reloadNavigation: false,
        //The URL for each link in the slider navigation bar, where <PAGE> is a placeholder for the page index
        navigationURL: window.location.toString(),
        //The text that is placed between each element in the slider navigation bar
        navigationSeparator: ' | ',
        //The HTML that should be placed inside the "prev" button in the navigation bar
        prevHtml: '&lt; Vorige',
        //The HTML that should be placed inside the "next" button in the navigation bar
        nextHtml: 'Volgende &gt;',

        /***
         *  Callback functions

         *  The current slider object is passed to each of these callback function, so that you can use its methods and objects in your callback functions
         ***/
        //A callback function that is called whenever the navigation bar is (re)loaded
        navigationLoadCallback: function(s) {},
        //A callback function that is called when the slider is first activated
        activateCallback: function(s) {},
        //A callback function that is called when the slider slides to a new element
        slideCallback: function(s) {}
    };

    if (typeof(params) != 'object' || params == null) {
        params = {};
    }

    this.params = $.extend({}, this.defaults, params);
    this.activeElement = null;
    this.count = 0;
    this.sliding = false;
    this.slidingTimeoutID = null;
    this.activated = false;
    this.navigationURLPagePlaceholder = '<PAGE>';

    this.sliderWrapper = $(wrapper);
    this.elements = this.sliderWrapper.find('.' + this.params.elementClass);
    this.navigation = this.sliderWrapper.find('.' + this.params.navigationClass);

    this.activate = function() {
        //If the slider was already activated, there's nothing left to do here
        if (this.activated)
            return;
        //Mark the slider as activated
        this.activated = true;
        //Hide all elements before we start setting up the slider
        this.elements.hide();
        //Hide the navigation bar
        if (this.params.createNavigation) {
            this.navigation.hide();
        }
        //Store the current object in a variable so we can use it in click events and other callback functions
        var slider = this;
        //Set the position of the sliderWrapper to relative so our absolute position will work correctly
        this.sliderWrapper.css('position', 'relative');
        //Store the count of slider elements in a separate value, because this won't change during execution
        this.count = this.elements.size();
        //Determine the actual starting element
        if (!(this.params.start instanceof jQuery)) {
            if (this.sliderWrapper.find('.' + this.params.elementClass + '.active').size()) {
                this.params.start = this.sliderWrapper.find('.' + this.params.elementClass + '.active').first();
            } else {
                this.params.start = this.elements.first();
            }
        }
        //Store the start element in our activeElement variable, because it will become our active element
        this.activeElement = this.params.start;
        //Display only the active element
        this.elements.removeClass('active');
        this.activeElement.show().addClass('active');
        //Set up number navigation
        if (this.params.createNavigation) {
            this.createNavigation();
        } else {
            this.updateNavigation();
        }
        //If a callback function for the activation event was specified, run it now
        if (typeof(this.params.activateCallback) == 'undefined') {
            this.params.activateCallback(this);
        }
        //If the slider should autoSlide, start the autoSlider
        if (this.params.autoSlide) {
            this.restartAutoSlide();
        }
    };

    this.prev = function() {
        nextElement = this.activeElement.prev();
        if (nextElement.length == 0 || !nextElement.hasClass(this.params.elementClass)) {
            nextElement = this.elements.last();
        }
        this.slideTo(nextElement);
    };

    this.next = function() {
        nextElement = this.activeElement.next();
        if (nextElement.length == 0 || !nextElement.hasClass(this.params.elementClass)) {
            nextElement = this.elements.first();
        }
        this.slideTo(nextElement);
    };

    this.slideTo = function(nextElement) {
        //If the next element is the same as the active element, don't slide
        if (this.getActiveIndex() == this.getElementIndex(nextElement)) {
            return false;
        }
        //If we're sliding at the moment, don't start a new slide
        if (this.sliding) {
            return false;
        }
        //If autoSlide is turned off, reset the timeout
        if (this.params.autoSlide) {
            this.restartAutoSlide();
        }

        var slider = this;

        //Prevent a double slide by setting the sliding variable to true for as long as we are sliding
        this.sliding = true;

        //Store the current height so we can use it later to determine if the new element is higher than the old element
        currentHeight = this.activeElement.height();

        //Position the current element absolutely, so that we can simulate a cross-fade effect
        this.activeElement.css({
            position: 'absolute',
            top: slider.params.topOffset,
            right: slider.params.rightOffset,
            bottom: slider.params.bottomOffset,
            left: slider.params.leftOffset
        });

        //Display the new element below the old one, so that it gradually becomes more visible
        //We temporarily set its height to the maximum of its own original height and the height of the previous picture, to smooth the transition for the navigation bar
        nextElement.addClass('active').css('display', 'block').css('height', Math.max(currentHeight, nextElement.height()) + "px");


        //Update the activeElement variable so we can update the navigation
        var prevElement = this.activeElement;
        this.activeElement = nextElement;
        //Either create a new navigation or update the old one, based on the specified parameters
        if (slider.params.reloadNavigation) {
            slider.createNavigation();
        } else {
            slider.updateNavigation();
        }

        prevElement.fadeOut(slider.params.fadeSpeed, function() {
            //When the element is done fading out, reset its position to its original value
            prevElement.removeClass('active').css({
               position: 'static',
               top: null,
               left: null
            });
            //Reset the height of the new element so that the navigation bar will be repositioned correctly (but only AFTER the element has been loaded)
            nextElement.css({
                height: "auto"
            });
            //We're no longer sliding, so set the sliding variable to false
            slider.sliding = false;
            //Run the onSlide callback function if it was defined
            if (typeof(slider.params.slideCallback) == 'function') {
                slider.params.slideCallback(slider);
            }
        });
        return false;
    };

    this.updateNavigation = function() {
        this.navigation.find('a.' + this.params.navigationElementClass).removeClass('active');
        this.navigation.find('a.' + this.params.navigationElementClass).eq(this.getActiveIndex()).addClass('active');
    };

    this.createNavigation = function() {
        //If there are not at least 2 elements in the slider, there's nothing to do here
        if (this.count <= 1) {
            this.navigation.remove();
            return false;
        }

        //Remove whatever content there was in the navigation bar before we fill it again
        this.navigation.empty();

        var slider = this;
        var pages = [];
        var activeIndex = this.getActiveIndex();

        //Check if we should display thumbnails or standard number navigation
        if (this.params.navigationThumbnails instanceof Array && this.params.navigationThumbnails.length == this.count) {
            for (i = 0; i < this.count; i++) {
                pages[i] = $('<img>').attr({
                    src: this.params.navigationThumbnails[i],
                    alt: (i+1).toString()
                });
            }
        }
        //If this.params.navigationThumbnails is a jQuery object, use the objects directly for our thumbnails
        else if (this.params.navigationThumbnails instanceof jQuery && this.params.navigationThumbnails.size() == this.count) {
            this.params.navigationThumbnails.each(function(i) {
                pages[i] = $(this);
            });
        }

        //If no thumbnails were specified, or if they were specified incorrectly, load a regular navigation bar with numbers representing elements
        else {
            //If there are more elements than we want to display in our navigation bar, handle it
            if (this.count > this.params.maxNavigationLength) {
                //The amount of elements to display to the left and to the right of the active page in the navigation bar
                var amp = Math.floor((this.params.maxNavigationLength - 3) / 2);
                pages[0] = 1;
                if (activeIndex - amp <= 2) {
                    for (i = 2; i <= amp * 2 + 2; i++) {
                        pages[pages.length] = i;
                    }
                } else if (activeIndex + amp >= this.count - 1) {
                    for (i = this.count - amp * 2 - 1; i < this.count; i++) {
                        pages[pages.length] = i;
                    }
                } else {
                    for (i = activeIndex - amp; i <= activeIndex + amp; i++) {
                        pages[pages.length] = i;
                    }
                }
                pages[pages.length] = this.count;
            }
            else {
                for (i = 0; i < this.count; i++) {
                    pages[i] = i+1;
                }
            }
        }

        //Add the tags to the navigation bar
        //Start with the "prev" button
        if (this.params.prevHtml != null) {
            this.navigation.append($('<span>').append($('<a>').html(this.params.prevHtml).attr('href', '#').addClass('prev').click(function() {
                slider.prev();
                return false;
            })));
            //Append a separator before we start adding the numbers
            if (this.params.navigationSeparator != null) {
                this.navigation.append($('<span>').html(this.params.navigationSeparator));
            }
        }

        //Add all number navigation elements (or thumbnails, if they were loaded)
        for (i = 0; i < pages.length; i++) {
            anchor = $('<a>').html(pages[i]);
            anchor.addClass(this.params.navigationElementClass).attr('href', slider.params.navigationURL.replace(new RegExp(this.navigationURLPagePlaceholder), (i+1).toString())).click(function() {
                slider.slideTo(slider.getElement(slider.getNavigationElementIndex($(this))));
                return false;
            });

            //Append the tag to the navigation bar
            this.navigation.append(anchor);

            //Append a separator
            if (this.params.navigationSeparator != null && i < this.count - 1) {
                this.navigation.append($('<span>').html(this.params.navigationSeparator));
            }
        }

        //Add the "next" button to the navigation
        if (this.params.nextHtml != null) {
            if (this.params.navigationSeparator != null) {
                this.navigation.append($('<span>').html(this.params.navigationSeparator));
            }
            this.navigation.append($('<a>').html(this.params.nextHtml).attr('href', '#').addClass('next').click(function() {
                slider.next();
                return false;
            }));
        }

        //Update the active element
        this.updateNavigation();

        //Run the callback function if it was specified
        if (typeof(this.params.navigationLoadCallback) == 'function') {
            this.params.navigationLoadCallback(this);
        }

        this.navigation.show();
    };

    //Restarts the autoSlide
    this.restartAutoSlide = function() {
        var slider = this;
        if (this.slidingTimeoutID != null) {
            clearTimeout(this.slidingTimeoutID);
        }
        //We use a timeout instead of an interval, because we want to reset the "interval" when a user slides to a new element
        this.slidingTimeoutID = setTimeout(function() {
            slider.next();
        }, this.params.autoSlideInterval);
    };

    this.getNavigationElement = function(i) {
        return this.navigation.find('a.' + this.params.navigationElementClass).eq(i);
    };

    this.getElement = function(i) {
        return this.elements.eq(i);
    };

    this.getNavigationElementIndex = function(el) {
        return el.prevAll('.' + this.params.navigationElementClass).size();
    };

    this.getElementIndex = function(el) {
        return el.prevAll('.' + this.params.elementClass).size();
    };

    this.getActiveIndex = function() {
        return this.getElementIndex(this.activeElement);
    };

    //Activate the slider right away, if it's meant to do so
    if (this.params.autoActivate) {
        this.activate();
    };
}
