/** 
  * 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:  true, 
                 autoStart:                 false, 
                 syncTransitions:           false, 
                 defaultTransitionDuration: 1000, 
                 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'); 
  
                                 // 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, 
                                         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(); 
                                  
                                 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(); 
                                          
                                         // 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" rel="history" href="#'+this.data[nextIndex].hash+'" 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);  
