/**

 * jQuery Galleriffic plugin

 *

 * Copyright (c) 2008 Trent Foley (http://trentacular.com)

 * Licensed under the MIT License:

 *   http://www.opensource.org/licenses/mit-license.php

 *

 * Much thanks to primary contributer Ponticlaro (http://www.ponticlaro.com)

 */

;(function($) {

    // Globally keep track of all images by their unique hash.  Each item is an image data object.

    var allImages = {};

    var imageCounter = 0;



    // Galleriffic static class

    $.galleriffic = {

        version: '2.0.1',



        // Strips invalid characters and any leading # characters

        normalizeHash: function(hash) {

            return hash.replace(/^.*#/, '').replace(/\?.*$/, '');

        },



        getImage: function(hash) {

            if (!hash)

                return undefined;



            hash = $.galleriffic.normalizeHash(hash);

            return allImages[hash];

        },



        // Global function that looks up an image by its hash and displays the image.

        // Returns false when an image is not found for the specified hash.

        // @param {String} hash This is the unique hash value assigned to an image.

        gotoImage: function(hash) {

            var imageData = $.galleriffic.getImage(hash);

            if (!imageData)

                return false;



            var gallery = imageData.gallery;

            gallery.gotoImage(imageData);

            

            return true;

        },



        // Removes an image from its respective gallery by its hash.

        // Returns false when an image is not found for the specified hash or the

        // specified owner gallery does match the located images gallery.

        // @param {String} hash This is the unique hash value assigned to an image.

        // @param {Object} ownerGallery (Optional) When supplied, the located images

        // gallery is verified to be the same as the specified owning gallery before

        // performing the remove operation.

        removeImageByHash: function(hash, ownerGallery) {

            var imageData = $.galleriffic.getImage(hash);

            if (!imageData)

                return false;



            var gallery = imageData.gallery;

            if (ownerGallery && ownerGallery != gallery)

                return false;



            return gallery.removeImageByIndex(imageData.index);

        }

    };



    var defaults = {

        delay:                     3000,

        numThumbs:                 20,

        preloadAhead:              40, // Set to -1 to preload all images

        enableTopPager:            false,

        enableBottomPager:         true,

        maxPagesToShow:            7,

        imageContainerSel:         '',

        captionContainerSel:       '',

        controlsContainerSel:      '',

        loadingContainerSel:       '',

        renderSSControls:          true,

        renderNavControls:         true,

        playLinkText:              'Play',

        pauseLinkText:             'Pause',

        prevLinkText:              'Previous',

        nextLinkText:              'Next',

        nextPageLinkText:          'Next &rsaquo;',

        prevPageLinkText:          '&lsaquo; Prev',

        enableHistory:             false,

        enableKeyboardNavigation:  false,

        autoStart:                 false,

        syncTransitions:           false,

        defaultTransitionDuration: 1000,

        forceWidth:                0,

        onSlideChange:             undefined, // accepts a delegate like such: function(prevIndex, nextIndex) { ... }

        onTransitionOut:           undefined, // accepts a delegate like such: function(slide, caption, isSync, callback) { ... }

        onTransitionIn:            undefined, // accepts a delegate like such: function(slide, caption, isSync) { ... }

        onPageTransitionOut:       undefined, // accepts a delegate like such: function(callback) { ... }

        onPageTransitionIn:        undefined, // accepts a delegate like such: function() { ... }

        onImageAdded:              undefined, // accepts a delegate like such: function(imageData, $li) { ... }

        onImageRemoved:            undefined  // accepts a delegate like such: function(imageData, $li) { ... }

    };



    // Primary Galleriffic initialization function that should be called on the thumbnail container.

    $.fn.galleriffic = function(settings) {

        //  Extend Gallery Object

        $.extend(this, {

            // Returns the version of the script

            version: $.galleriffic.version,



            // Current state of the slideshow

            isSlideshowRunning: false,

            slideshowTimeout: undefined,



            // This function is attached to the click event of generated hyperlinks within the gallery

            clickHandler: function(e, link) {

                this.pause();



                if (!this.enableHistory) {

                    // The href attribute holds the unique hash for an image

                    var hash = $.galleriffic.normalizeHash($(link).attr('href'));

                    $.galleriffic.gotoImage(hash);

                    e.preventDefault();

                }

            },



            // Appends an image to the end of the set of images.  Argument listItem can be either a jQuery DOM element or arbitrary html.

            // @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery.

            appendImage: function(listItem) {

                this.addImage(listItem, false, false);

                return this;

            },



            // Inserts an image into the set of images.  Argument listItem can be either a jQuery DOM element or arbitrary html.

            // @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery.

            // @param {Integer} position The index within the gallery where the item shouold be added.

            insertImage: function(listItem, position) {

                this.addImage(listItem, false, true, position);

                return this;

            },



            // Adds an image to the gallery and optionally inserts/appends it to the DOM (thumbExists)

            // @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery.

            // @param {Boolean} thumbExists Specifies whether the thumbnail already exists in the DOM or if it needs to be added.

            // @param {Boolean} insert Specifies whether the the image is appended to the end or inserted into the gallery.

            // @param {Integer} position The index within the gallery where the item shouold be added.

            addImage: function(listItem, thumbExists, insert, position) {

                var $li = ( typeof listItem === "string" ) ? $(listItem) : listItem;                

                var $aThumb = $li.find('a.thumb');

                var slideUrl = $aThumb.attr('href');

                var title = $aThumb.attr('title');

                var $caption = $li.find('.caption').remove();

                var hash = $aThumb.attr('name');

                var alt = $aThumb.attr('alt');



                // Increment the image counter

                imageCounter++;



                // Autogenerate a hash value if none is present or if it is a duplicate

                if (!hash || allImages[''+hash]) {

                    hash = imageCounter;

                }



                // Set position to end when not specified

                if (!insert)

                    position = this.data.length;

                

                var imageData = {

                    title:title,

                    slideUrl:slideUrl,

                    caption:$caption,

                    hash:hash,

                    gallery:this,

                    alt:alt,

                    index:position

                };



                // Add the imageData to this gallery's array of images

                if (insert) {

                    this.data.splice(position, 0, imageData);



                    // Reset index value on all imageData objects

                    this.updateIndices(position);

                }

                else {

                    this.data.push(imageData);

                }



                var gallery = this;



                // Add the element to the DOM

                if (!thumbExists) {

                    // Update thumbs passing in addition post transition out handler

                    this.updateThumbs(function() {

                        var $thumbsUl = gallery.find('ul.thumbs');

                        if (insert)

                            $thumbsUl.children(':eq('+position+')').before($li);

                        else

                            $thumbsUl.append($li);

                        

                        if (gallery.onImageAdded)

                            gallery.onImageAdded(imageData, $li);

                    });

                }



                // Register the image globally

                allImages[''+hash] = imageData;



                // Setup attributes and click handler

                $aThumb.attr('rel', 'history')

                    .attr('href', '#'+hash)

                    .removeAttr('name')

                    .click(function(e) {

                        gallery.clickHandler(e, this);

                    });



                return this;

            },



            // Removes an image from the gallery based on its index.

            // Returns false when the index is out of range.

            removeImageByIndex: function(index) {

                if (index < 0 || index >= this.data.length)

                    return false;

                

                var imageData = this.data[index];

                if (!imageData)

                    return false;

                

                this.removeImage(imageData);

                

                return true;

            },



            // Convenience method that simply calls the global removeImageByHash method.

            removeImageByHash: function(hash) {

                return $.galleriffic.removeImageByHash(hash, this);

            },



            // Removes an image from the gallery.

            removeImage: function(imageData) {

                var index = imageData.index;

                

                // Remove the image from the gallery data array

                this.data.splice(index, 1);

                

                // Remove the global registration

                delete allImages[''+imageData.hash];

                

                // Remove the image's list item from the DOM

                this.updateThumbs(function() {

                    var $li = gallery.find('ul.thumbs')

                        .children(':eq('+index+')')

                        .remove();



                    if (gallery.onImageRemoved)

                        gallery.onImageRemoved(imageData, $li);

                });



                // Update each image objects index value

                this.updateIndices(index);



                return this;

            },



            // Updates the index values of the each of the images in the gallery after the specified index

            updateIndices: function(startIndex) {

                for (i = startIndex; i < this.data.length; i++) {

                    this.data[i].index = i;

                }

                

                return this;

            },



            // Scraped the thumbnail container for thumbs and adds each to the gallery

            initializeThumbs: function() {

                this.data = [];

                var gallery = this;



                this.find('ul.thumbs > li').each(function(i) {

                    gallery.addImage($(this), true, false);

                });



                return this;

            },



            isPreloadComplete: false,



            // Initalizes the image preloader

            preloadInit: function() {

                if (this.preloadAhead == 0) return this;

                

                this.preloadStartIndex = this.currentImage.index;

                var nextIndex = this.getNextIndex(this.preloadStartIndex);

                return this.preloadRecursive(this.preloadStartIndex, nextIndex);

            },



            // Changes the location in the gallery the preloader should work

            // @param {Integer} index The index of the image where the preloader should restart at.

            preloadRelocate: function(index) {

                // By changing this startIndex, the current preload script will restart

                this.preloadStartIndex = index;

                return this;

            },



            // Recursive function that performs the image preloading

            // @param {Integer} startIndex The index of the first image the current preloader started on.

            // @param {Integer} currentIndex The index of the current image to preload.

            preloadRecursive: function(startIndex, currentIndex) {

                // Check if startIndex has been relocated

                if (startIndex != this.preloadStartIndex) {

                    var nextIndex = this.getNextIndex(this.preloadStartIndex);

                    return this.preloadRecursive(this.preloadStartIndex, nextIndex);

                }



                var gallery = this;



                // Now check for preloadAhead count

                var preloadCount = currentIndex - startIndex;

                if (preloadCount < 0)

                    preloadCount = this.data.length-1-startIndex+currentIndex;

                if (this.preloadAhead >= 0 && preloadCount > this.preloadAhead) {

                    // Do this in order to keep checking for relocated start index

                    setTimeout(function() { gallery.preloadRecursive(startIndex, currentIndex); }, 500);

                    return this;

                }



                var imageData = this.data[currentIndex];

                if (!imageData)

                    return this;



                // If already loaded, continue

                if (imageData.image)

                    return this.preloadNext(startIndex, currentIndex); 

                

                // Preload the image

                var image = new Image();

                

                if(this.forceWidth && this.forceWidth > 0)

                    image.width = this.forceWidth;              

                

                image.onload = function() {

                    imageData.image = this;

                    gallery.preloadNext(startIndex, currentIndex);

                };



                image.alt = imageData.title;

                image.src = imageData.slideUrl;



                return this;

            },

            

            // Called by preloadRecursive in order to preload the next image after the previous has loaded.

            // @param {Integer} startIndex The index of the first image the current preloader started on.

            // @param {Integer} currentIndex The index of the current image to preload.

            preloadNext: function(startIndex, currentIndex) {

                var nextIndex = this.getNextIndex(currentIndex);

                if (nextIndex == startIndex) {

                    this.isPreloadComplete = true;

                } else {

                    // Use setTimeout to free up thread

                    var gallery = this;

                    setTimeout(function() { gallery.preloadRecursive(startIndex, nextIndex); }, 100);

                }



                return this;

            },



            // Safe way to get the next image index relative to the current image.

            // If the current image is the last, returns 0

            getNextIndex: function(index) {

                var nextIndex = index+1;

                if (nextIndex >= this.data.length)

                    nextIndex = 0;

                return nextIndex;

            },



            // Safe way to get the previous image index relative to the current image.

            // If the current image is the first, return the index of the last image in the gallery.

            getPrevIndex: function(index) {

                var prevIndex = index-1;

                if (prevIndex < 0)

                    prevIndex = this.data.length-1;

                return prevIndex;

            },



            // Pauses the slideshow

            pause: function() {

                this.isSlideshowRunning = false;

                if (this.slideshowTimeout) {

                    clearTimeout(this.slideshowTimeout);

                    this.slideshowTimeout = undefined;

                }



                if (this.$controlsContainer) {

                    this.$controlsContainer

                        .find('div.ss-controls a').removeClass().addClass('play')

                        .attr('title', this.playLinkText)

                        .attr('href', '#play')

                        .html(this.playLinkText);

                }

                

                return this;

            },



            // Plays the slideshow

            play: function() {

                this.isSlideshowRunning = true;



                if (this.$controlsContainer) {

                    this.$controlsContainer

                        .find('div.ss-controls a').removeClass().addClass('pause')

                        .attr('title', this.pauseLinkText)

                        .attr('href', '#pause')

                        .html(this.pauseLinkText);

                }



                if (!this.slideshowTimeout) {

                    var gallery = this;

                    this.slideshowTimeout = setTimeout(function() { gallery.ssAdvance(); }, this.delay);

                }



                return this;

            },



            // Toggles the state of the slideshow (playing/paused)

            toggleSlideshow: function() {

                if (this.isSlideshowRunning)

                    this.pause();

                else

                    this.play();



                return this;

            },



            // Advances the slideshow to the next image and delegates navigation to the

            // history plugin when history is enabled

            // enableHistory is true

            ssAdvance: function() {

                if (this.isSlideshowRunning)

                    this.next(true);



                return this;

            },



            // Advances the gallery to the next image.

            // @param {Boolean} dontPause Specifies whether to pause the slideshow.

            // @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled.  

            next: function(dontPause, bypassHistory) {

                this.gotoIndex(this.getNextIndex(this.currentImage.index), dontPause, bypassHistory);

                return this;

            },



            // Navigates to the previous image in the gallery.

            // @param {Boolean} dontPause Specifies whether to pause the slideshow.

            // @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled.

            previous: function(dontPause, bypassHistory) {

                this.gotoIndex(this.getPrevIndex(this.currentImage.index), dontPause, bypassHistory);

                return this;

            },



            // Navigates to the next page in the gallery.

            // @param {Boolean} dontPause Specifies whether to pause the slideshow.

            // @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled.

            nextPage: function(dontPause, bypassHistory) {

                var page = this.getCurrentPage();

                var lastPage = this.getNumPages() - 1;

                if (page < lastPage) {

                    var startIndex = page * this.numThumbs;

                    var nextPage = startIndex + this.numThumbs;

                    this.gotoIndex(nextPage, dontPause, bypassHistory);

                }



                return this;

            },



            // Navigates to the previous page in the gallery.

            // @param {Boolean} dontPause Specifies whether to pause the slideshow.

            // @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled.

            previousPage: function(dontPause, bypassHistory) {

                var page = this.getCurrentPage();

                if (page > 0) {

                    var startIndex = page * this.numThumbs;

                    var prevPage = startIndex - this.numThumbs;             

                    this.gotoIndex(prevPage, dontPause, bypassHistory);

                }

                

                return this;

            },



            // Navigates to the image at the specified index in the gallery

            // @param {Integer} index The index of the image in the gallery to display.

            // @param {Boolean} dontPause Specifies whether to pause the slideshow.

            // @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled.

            gotoIndex: function(index, dontPause, bypassHistory) {

                if (!dontPause)

                    this.pause();

                

                if (index < 0) index = 0;

                else if (index >= this.data.length) index = this.data.length-1;

                

                var imageData = this.data[index];

                

                if (!bypassHistory && this.enableHistory)

                    $.historyLoad(String(imageData.hash));  // At the moment, historyLoad only accepts string arguments

                else

                    this.gotoImage(imageData);



                return this;

            },



            // This function is garaunteed to be called anytime a gallery slide changes.

            // @param {Object} imageData An object holding the image metadata of the image to navigate to.

            gotoImage: function(imageData) {

                var index = imageData.index;



                if (this.onSlideChange)

                    this.onSlideChange(this.currentImage.index, index);

                

                this.currentImage = imageData;

                this.preloadRelocate(index);

                

                this.refresh();

                

                return this;

            },



            // Returns the default transition duration value.  The value is halved when not

            // performing a synchronized transition.

            // @param {Boolean} isSync Specifies whether the transitions are synchronized.

            getDefaultTransitionDuration: function(isSync) {

                if (isSync)

                    return this.defaultTransitionDuration;

                return this.defaultTransitionDuration / 2;

            },



            // Rebuilds the slideshow image and controls and performs transitions

            refresh: function() {

                var imageData = this.currentImage;

                if (!imageData)

                    return this;



                var index = imageData.index;



                // Update Controls

                if (this.$controlsContainer) {

                    this.$controlsContainer

                        .find('div.nav-controls a.prev').attr('href', '#'+this.data[this.getPrevIndex(index)].hash).end()

                        .find('div.nav-controls a.next').attr('href', '#'+this.data[this.getNextIndex(index)].hash);

                }



                var previousSlide = this.$imageContainer.find('span.current').addClass('previous').removeClass('current');

                var previousCaption = 0;



                if (this.$captionContainer) {

                    previousCaption = this.$captionContainer.find('span.current').addClass('previous').removeClass('current');

                }



                // Perform transitions simultaneously if syncTransitions is true and the next image is already preloaded

                var isSync = this.syncTransitions && imageData.image;



                // Flag we are transitioning

                var isTransitioning = true;

                var gallery = this;



                var transitionOutCallback = function() {

                    // Flag that the transition has completed

                    isTransitioning = false;



                    // Remove the old slide

                    previousSlide.remove();



                    // Remove old caption

                    if (previousCaption)

                        previousCaption.remove();



                    if (!isSync) {

                        if (imageData.image && imageData.hash == gallery.data[gallery.currentImage.index].hash) {

                            gallery.buildImage(imageData, isSync);

                        } else {

                            // Show loading container

                            if (gallery.$loadingContainer) {

                                gallery.$loadingContainer.show();

                            }

                        }

                    }

                };



                if (previousSlide.length == 0) {

                    // For the first slide, the previous slide will be empty, so we will call the callback immediately

                    transitionOutCallback();

                } else {

                    if (this.onTransitionOut) {

                        this.onTransitionOut(previousSlide, previousCaption, isSync, transitionOutCallback);

                    } else {

                        previousSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0, transitionOutCallback);

                        if (previousCaption)

                            previousCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0);

                    }

                }



                // Go ahead and begin transitioning in of next image

                if (isSync)

                    this.buildImage(imageData, isSync);



                if (!imageData.image) {

                    var image = new Image();                    

                    

                    if(this.forceWidth && this.forceWidth > 0)

                        image.width = this.forceWidth;                  

                        

                    // Wire up mainImage onload event

                    image.onload = function() {

                        imageData.image = this;



                        // Only build image if the out transition has completed and we are still on the same image hash

                        if (!isTransitioning && imageData.hash == gallery.data[gallery.currentImage.index].hash) {

                            gallery.buildImage(imageData, isSync);

                        }

                    };



                    // set alt and src

                    image.alt = imageData.title;

                    image.src = imageData.slideUrl;

                    



                }



                // This causes the preloader (if still running) to relocate out from the currentIndex

                this.relocatePreload = true;



                return this.syncThumbs();

            },



            // Called by the refresh method after the previous image has been transitioned out or at the same time

            // as the out transition when performing a synchronous transition.

            // @param {Object} imageData An object holding the image metadata of the image to build.

            // @param {Boolean} isSync Specifies whether the transitions are synchronized.

            buildImage: function(imageData, isSync) {

                var gallery = this;

                var nextIndex = this.getNextIndex(imageData.index);



                // Construct new hidden span for the image

                var newSlide = this.$imageContainer

                    .append('<span class="image-wrapper current"><a class="advance-link" id="item'+this.data[nextIndex].hash+'" rel="history" href="'+imageData.alt+'" title="'+imageData.title+'">&nbsp;</a></span>')

                    .find('span.current').css('opacity', '0');

                    

                newSlide.find('a')

                    .append(imageData.image)

                    /*.click(function(e) {

                        gallery.clickHandler(e, this);

                    });*/;

                

                var newCaption = 0;

                if (this.$captionContainer) {

                    // Construct new hidden caption for the image

                    newCaption = this.$captionContainer

                        .append('<span class="image-caption current"></span>')

                        .find('span.current').css('opacity', '0')

                        .append(imageData.caption);

                }



                // Hide the loading conatiner

                if (this.$loadingContainer) {

                    this.$loadingContainer.hide();

                }



                // Transition in the new image

                if (this.onTransitionIn) {

                    this.onTransitionIn(newSlide, newCaption, isSync);

                } else {

                    newSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);

                    if (newCaption)

                        newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);

                }

                

                if (this.isSlideshowRunning) {

                    if (this.slideshowTimeout)

                        clearTimeout(this.slideshowTimeout);



                    this.slideshowTimeout = setTimeout(function() { gallery.ssAdvance(); }, this.delay);

                }



                return this;

            },



            // Returns the current page index that should be shown for the currentImage

            getCurrentPage: function() {

                return Math.floor(this.currentImage.index / this.numThumbs);

            },



            // Applies the selected class to the current image's corresponding thumbnail.

            // Also checks if the current page has changed and updates the displayed page of thumbnails if necessary.

            syncThumbs: function() {

                var page = this.getCurrentPage();

                if (page != this.displayedPage)

                    this.updateThumbs();



                // Remove existing selected class and add selected class to new thumb

                var $thumbs = this.find('ul.thumbs').children();

                $thumbs.filter('.selected').removeClass('selected');

                $thumbs.eq(this.currentImage.index).addClass('selected');



                return this;

            },



            // Performs transitions on the thumbnails container and updates the set of

            // thumbnails that are to be displayed and the navigation controls.

            // @param {Delegate} postTransitionOutHandler An optional delegate that is called after

            // the thumbnails container has transitioned out and before the thumbnails are rebuilt.

            updateThumbs: function(postTransitionOutHandler) {

                var gallery = this;

                var transitionOutCallback = function() {

                    // Call the Post-transition Out Handler

                    if (postTransitionOutHandler)

                        postTransitionOutHandler();

                    

                    gallery.rebuildThumbs();



                    // Transition In the thumbsContainer

                    if (gallery.onPageTransitionIn)

                        gallery.onPageTransitionIn();

                    else

                        gallery.show();

                };



                // Transition Out the thumbsContainer

                if (this.onPageTransitionOut) {

                    this.onPageTransitionOut(transitionOutCallback);

                } else {

                    this.hide();

                    transitionOutCallback();

                }



                return this;

            },



            // Updates the set of thumbnails that are to be displayed and the navigation controls.

            rebuildThumbs: function() {

                var needsPagination = this.data.length > this.numThumbs;



                // Rebuild top pager

                if (this.enableTopPager) {

                    var $topPager = this.find('div.top');

                    if ($topPager.length == 0)

                        $topPager = this.prepend('<div class="top pagination"></div>').find('div.top');

                    else

                        $topPager.empty();



                    if (needsPagination)

                        this.buildPager($topPager);

                }



                // Rebuild bottom pager

                if (this.enableBottomPager) {

                    var $bottomPager = this.find('div.bottom');

                    if ($bottomPager.length == 0)

                        $bottomPager = this.append('<div class="bottom pagination"></div>').find('div.bottom');

                    else

                        $bottomPager.empty();



                    if (needsPagination)

                        this.buildPager($bottomPager);

                }



                var page = this.getCurrentPage();

                var startIndex = page*this.numThumbs;

                var stopIndex = startIndex+this.numThumbs-1;

                if (stopIndex >= this.data.length)

                    stopIndex = this.data.length-1;



                // Show/Hide thumbs

                var $thumbsUl = this.find('ul.thumbs');

                $thumbsUl.find('li').each(function(i) {

                    var $li = $(this);

                    if (i >= startIndex && i <= stopIndex) {

                        $li.show();

                    } else {

                        $li.hide();

                    }

                });



                this.displayedPage = page;



                // Remove the noscript class from the thumbs container ul

                $thumbsUl.removeClass('noscript');

                

                return this;

            },



            // Returns the total number of pages required to display all the thumbnails.

            getNumPages: function() {

                return Math.ceil(this.data.length/this.numThumbs);

            },



            // Rebuilds the pager control in the specified matched element.

            // @param {jQuery} pager A jQuery element set matching the particular pager to be rebuilt.

            buildPager: function(pager) {

                var gallery = this;

                var numPages = this.getNumPages();

                var page = this.getCurrentPage();

                var startIndex = page * this.numThumbs;

                var pagesRemaining = this.maxPagesToShow - 1;

                

                var pageNum = page - Math.floor((this.maxPagesToShow - 1) / 2) + 1;

                if (pageNum > 0) {

                    var remainingPageCount = numPages - pageNum;

                    if (remainingPageCount < pagesRemaining) {

                        pageNum = pageNum - (pagesRemaining - remainingPageCount);

                    }

                }



                if (pageNum < 0) {

                    pageNum = 0;

                }



                // Prev Page Link

                if (page > 0) {

                    var prevPage = startIndex - this.numThumbs;

                    pager.append('<a rel="history" href="#'+this.data[prevPage].hash+'" title="'+this.prevPageLinkText+'">'+this.prevPageLinkText+'</a>');

                }



                // Create First Page link if needed

                if (pageNum > 0) {

                    this.buildPageLink(pager, 0, numPages);

                    if (pageNum > 1)

                        pager.append('<span class="ellipsis">&hellip;</span>');

                    

                    pagesRemaining--;

                }



                // Page Index Links

                while (pagesRemaining > 0) {

                    this.buildPageLink(pager, pageNum, numPages);

                    pagesRemaining--;

                    pageNum++;

                }



                // Create Last Page link if needed

                if (pageNum < numPages) {

                    var lastPageNum = numPages - 1;

                    if (pageNum < lastPageNum)

                        pager.append('<span class="ellipsis">&hellip;</span>');



                    this.buildPageLink(pager, lastPageNum, numPages);

                }



                // Next Page Link

                var nextPage = startIndex + this.numThumbs;

                if (nextPage < this.data.length) {

                    pager.append('<a rel="history" href="#'+this.data[nextPage].hash+'" title="'+this.nextPageLinkText+'">'+this.nextPageLinkText+'</a>');

                }



                pager.find('a').click(function(e) {

                    gallery.clickHandler(e, this);

                });



                return this;

            },



            // Builds a single page link within a pager.  This function is called by buildPager

            // @param {jQuery} pager A jQuery element set matching the particular pager to be rebuilt.

            // @param {Integer} pageNum The page number of the page link to build.

            // @param {Integer} numPages The total number of pages required to display all thumbnails.

            buildPageLink: function(pager, pageNum, numPages) {

                var pageLabel = pageNum + 1;

                var currentPage = this.getCurrentPage();

                if (pageNum == currentPage)

                    pager.append('<span class="current">'+pageLabel+'</span>');

                else if (pageNum < numPages) {

                    var imageIndex = pageNum*this.numThumbs;

                    pager.append('<a rel="history" href="#'+this.data[imageIndex].hash+'" title="'+pageLabel+'">'+pageLabel+'</a>');

                }

                

                return this;

            }

        });



        // Now initialize the gallery

        $.extend(this, defaults, settings);

        

        // Verify the history plugin is available

        if (this.enableHistory && !$.historyInit)

            this.enableHistory = false;

        

        // Select containers

        if (this.imageContainerSel) this.$imageContainer = $(this.imageContainerSel);

        if (this.captionContainerSel) this.$captionContainer = $(this.captionContainerSel);

        if (this.loadingContainerSel) this.$loadingContainer = $(this.loadingContainerSel);



        // Initialize the thumbails

        this.initializeThumbs();

        

        if (this.maxPagesToShow < 3)

            this.maxPagesToShow = 3;



        this.displayedPage = -1;

        this.currentImage = this.data[0];

        var gallery = this;



        // Hide the loadingContainer

        if (this.$loadingContainer)

            this.$loadingContainer.hide();



        // Setup controls

        if (this.controlsContainerSel) {

            this.$controlsContainer = $(this.controlsContainerSel).empty();

            

            if (this.renderSSControls) {

                if (this.autoStart) {

                    this.$controlsContainer

                        .append('<div class="ss-controls"><a href="#pause" class="pause" title="'+this.pauseLinkText+'">'+this.pauseLinkText+'</a></div>');

                } else {

                    this.$controlsContainer

                        .append('<div class="ss-controls"><a href="#play" class="play" title="'+this.playLinkText+'">'+this.playLinkText+'</a></div>');

                }



                this.$controlsContainer.find('div.ss-controls a')

                    .click(function(e) {

                        gallery.toggleSlideshow();

                        e.preventDefault();

                        return false;

                    });

            }

        

            if (this.renderNavControls) {

                this.$controlsContainer

                    .append('<div class="nav-controls"><a class="prev" rel="history" title="'+this.prevLinkText+'">'+this.prevLinkText+'</a><a class="next" rel="history" title="'+this.nextLinkText+'">'+this.nextLinkText+'</a></div>')

                    .find('div.nav-controls a')

                    .click(function(e) {

                        gallery.clickHandler(e, this);

                    });

            }

        }



        var initFirstImage = !this.enableHistory || !location.hash;

        if (this.enableHistory && location.hash) {

            var hash = $.galleriffic.normalizeHash(location.hash);

            var imageData = allImages[hash];

            if (!imageData)

                initFirstImage = true;

        }



        // Setup gallery to show the first image

        if (initFirstImage)

            this.gotoIndex(0, false, true);



        // Setup Keyboard Navigation

        if (this.enableKeyboardNavigation) {

            $(document).keydown(function(e) {

                var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;

                switch(key) {

                    case 32: // space

                        gallery.next();

                        e.preventDefault();

                        break;

                    case 33: // Page Up

                        gallery.previousPage();

                        e.preventDefault();

                        break;

                    case 34: // Page Down

                        gallery.nextPage();

                        e.preventDefault();

                        break;

                    case 35: // End

                        gallery.gotoIndex(gallery.data.length-1);

                        e.preventDefault();

                        break;

                    case 36: // Home

                        gallery.gotoIndex(0);

                        e.preventDefault();

                        break;

                    case 37: // left arrow

                        gallery.previous();

                        e.preventDefault();

                        break;

                    case 39: // right arrow

                        gallery.next();

                        e.preventDefault();

                        break;

                }

            });

        }



        // Auto start the slideshow

        if (this.autoStart)

            this.play();



        // Kickoff Image Preloader after 1 second

        setTimeout(function() { gallery.preloadInit(); }, 1000);



        return this;

    };

})(jQuery);





;(function($) {

    var defaults = {

        mouseOutOpacity:   0.67,

        mouseOverOpacity:  1.0,

        fadeSpeed:         'fast',

        exemptionSelector: '.selected'

    };



    $.fn.opacityrollover = function(settings) {

        // Initialize the effect

        $.extend(this, defaults, settings);



        var config = this;



        function fadeTo(element, opacity) {

            var $target = $(element);

            

            if (config.exemptionSelector)

                $target = $target.not(config.exemptionSelector);    

            

            $target.fadeTo(config.fadeSpeed, opacity);

        }



        this.css('opacity', this.mouseOutOpacity)

            .hover(

                function () {

                    fadeTo(this, config.mouseOverOpacity);

                },

                function () {

                    fadeTo(this, config.mouseOutOpacity);

                });



        return this;

    };

})(jQuery);




