//eval(function(p, a, c, k, e, r) { e = function(c) { return (c < a ? '' : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) }; if (!''.replace(/^/, String)) { while (c--) r[e(c)] = k[c] || e(c); k = [function(e) { return r[e] } ]; e = function() { return '\\w+' }; c = 1 }; while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); return p } (';(8($){6 17={};6 1P=0;$.F={1Q:\'2.0.1\',1x:8(f){b f.2B(/^.*#/,\'\').2B(/\\?.*$/,\'\')},1R:8(f){5(!f)b G;f=$.F.1x(f);b 17[f]},1e:8(f){6 c=$.F.1R(f);5(!c)b k;6 9=c.9;9.1e(c);b r},1S:8(f,1T){6 c=$.F.1R(f);5(!c)b k;6 9=c.9;5(1T&&1T!=9)b k;b 9.2C(c.d)}};6 2D={1U:3t,w:20,1y:40,2E:k,2F:r,1f:7,1V:\'\',1W:\'\',1X:\'\',1Y:\'\',2G:r,2H:r,1g:\'3u\',1h:\'3v\',1Z:\'3w\',21:\'2I\',22:\'2I &3x;\',23:\'&3y; 3z\',U:k,2J:r,24:k,2K:k,25:2L,26:G,27:G,28:G,29:G,2a:G,2b:G,2c:G};$.3A.F=8(2M){$.2N(4,{1Q:$.F.1Q,18:k,J:G,1i:8(e,2d){4.P();5(!4.U){6 f=$.F.1x($(2d).B(\'y\'));$.F.1e(f);e.K()}},3B:8(Q){4.1z(Q,k,k);b 4},3C:8(Q,R){4.1z(Q,k,r,R);b 4},1z:8(Q,2O,1A,R){6 $z=(3D Q==="3E")?$(Q):Q;6 $1j=$z.h(\'a.3F\');6 1k=$1j.B(\'y\');6 n=$1j.B(\'n\');6 $19=$z.h(\'.19\').1B();6 f=$1j.B(\'2P\');1P++;5(!f||17[\'\'+f]){f=1P}5(!1A)R=4.g.o;6 c={n:n,1k:1k,19:$19,f:f,9:4,d:R};5(1A){4.g.2Q(R,0,c);4.2e(R)}p{4.g.3G(c)}6 9=4;5(!2O){4.1C(8(){6 $1a=9.h(\'1l.S\');5(1A)$1a.2f(\':2g(\'+R+\')\').3H($z);p $1a.s($z);5(9.2b)9.2b(c,$z)})}17[\'\'+f]=c;$1j.B(\'V\',\'W\').B(\'y\',\'#\'+f).3I(\'2P\').1m(8(e){9.1i(e,4)});b 4},2C:8(d){5(d<0||d>=4.g.o)b k;6 c=4.g[d];5(!c)b k;4.2R(c);b r},1S:8(f){b $.F.1S(f,4)},2R:8(c){6 d=c.d;4.g.2Q(d,1);3J 17[\'\'+c.f];4.1C(8(){6 $z=9.h(\'1l.S\').2f(\':2g(\'+d+\')\').1B();5(9.2c)9.2c(c,$z)});4.2e(d);b 4},2e:8(j){3K(i=j;i<4.g.o;i++){4.g[i].d=i}b 4},2S:8(){4.g=[];6 9=4;4.h(\'1l.S > z\').2T(8(i){9.1z($(4),r,k)});b 4},2U:k,2V:8(){5(4.1y==0)b 4;4.X=4.D.d;6 A=4.Y(4.X);b 4.1n(4.X,A)},2W:8(d){4.X=d;b 4},1n:8(j,L){5(j!=4.X){6 A=4.Y(4.X);b 4.1n(4.X,A)}6 9=4;6 1D=L-j;5(1D<0)1D=4.g.o-1-j+L;5(4.1y>=0&&1D>4.1y){1o(8(){9.1n(j,L)},3L);b 4}6 c=4.g[L];5(!c)b 4;5(c.q)b 4.2h(j,L);6 q=2X 2Y();q.2Z=8(){c.q=4;9.2h(j,L)};q.30=c.n;q.31=c.1k;b 4},2h:8(j,L){6 A=4.Y(L);5(A==j){4.2U=r}p{6 9=4;1o(8(){9.1n(j,A)},3M)}b 4},Y:8(d){6 A=d+1;5(A>=4.g.o)A=0;b A},2i:8(d){6 1E=d-1;5(1E<0)1E=4.g.o-1;b 1E},P:8(){4.18=k;5(4.J){38(4.J);4.J=G}5(4.$E){4.$E.h(\'l.1p-M a\').1b().1q(\'Z\').B(\'n\',4.1g).B(\'y\',\'#Z\').3a(4.1g)}b 4},Z:8(){4.18=r;5(4.$E){4.$E.h(\'l.1p-M a\').1b().1q(\'P\').B(\'n\',4.1h).B(\'y\',\'#P\').3a(4.1h)}5(!4.J){6 9=4;4.J=1o(8(){9.2j()},4.1U)}b 4},3b:8(){5(4.18)4.P();p 4.Z();b 4},2j:8(){5(4.18)4.1c(r);b 4},1c:8(H,I){4.T(4.Y(4.D.d),H,I);b 4},1F:8(H,I){4.T(4.2i(4.D.d),H,I);b 4},10:8(H,I){6 t=4.11();6 3c=4.2k()-1;5(t<3c){6 j=t*4.w;6 10=j+4.w;4.T(10,H,I)}b 4},3d:8(H,I){6 t=4.11();5(t>0){6 j=t*4.w;6 1G=j-4.w;4.T(1G,H,I)}b 4},T:8(d,H,I){5(!H)4.P();5(d<0)d=0;p 5(d>=4.g.o)d=4.g.o-1;6 c=4.g[d];5(!I&&4.U)$.3N(3O(c.f));p 4.1e(c);b 4},1e:8(c){6 d=c.d;5(4.26)4.26(4.D.d,d);4.D=c;4.2W(d);4.3e();b 4},1r:8(u){5(u)b 4.25;b 4.25/2},3e:8(){6 c=4.D;5(!c)b 4;6 d=c.d;5(4.$E){4.$E.h(\'l.1H-M a.3f\').B(\'y\',\'#\'+4.g[4.2i(d)].f).3P().h(\'l.1H-M a.1c\').B(\'y\',\'#\'+4.g[4.Y(d)].f)}6 1s=4.$2l.h(\'x.N\').1q(\'1F\').1b(\'N\');6 12=0;5(4.$1t){12=4.$1t.h(\'x.N\').1q(\'1F\').1b(\'N\')}6 u=4.2K&&c.q;6 2m=r;6 9=4;6 13=8(){2m=k;1s.1B();5(12)12.1B();5(!u){5(c.q&&c.f==9.g[9.D.d].f){9.1I(c,u)}p{5(9.$14){9.$14.2n()}}}};5(1s.o==0){13()}p{5(4.27){4.27(1s,12,u,13)}p{1s.1J(4.1r(u),0.0,13);5(12)12.1J(4.1r(u),0.0)}}5(u)4.1I(c,u);5(!c.q){6 q=2X 2Y();q.2Z=8(){c.q=4;5(!2m&&c.f==9.g[9.D.d].f){9.1I(c,u)}};q.30=c.n;q.31=c.1k}4.3Q=r;b 4.3g()},1I:8(c,u){6 9=4;6 A=4.Y(c.d);6 1K=4.$2l.s(\'<x v="q-3R N"><a v="3S-2d" V="W" y="#\'+4.g[A].f+\'" n="\'+c.n+\'">&3T;</a></x>\').h(\'x.N\').3h(\'3i\',\'0\');1K.h(\'a\').s(c.q).1m(8(e){9.1i(e,4)});6 1u=0;5(4.$1t){1u=4.$1t.s(\'<x v="q-19 N"></x>\').h(\'x.N\').3h(\'3i\',\'0\').s(c.19)}5(4.$14){4.$14.1L()}5(4.28){4.28(1K,1u,u)}p{1K.1J(4.1r(u),1.0);5(1u)1u.1J(4.1r(u),1.0)}5(4.18){5(4.J)38(4.J);4.J=1o(8(){9.2j()},4.1U)}b 4},11:8(){b 2o.3j(4.D.d/4.w)},3g:8(){6 t=4.11();5(t!=4.2p)4.1C();6 $S=4.h(\'1l.S\').2f();$S.3U(\'.2q\').1b(\'2q\');$S.2g(4.D.d).1q(\'2q\');b 4},1C:8(2r){6 9=4;6 13=8(){5(2r)2r();9.3k();5(9.2a)9.2a();p 9.2n()};5(4.29){4.29(13)}p{4.1L();13()}b 4},3k:8(){6 2s=4.g.o>4.w;5(4.2E){6 $1v=4.h(\'l.2t\');5($1v.o==0)$1v=4.3V(\'<l v="2t 3l"></l>\').h(\'l.2t\');p $1v.2u();5(2s)4.2v($1v)}5(4.2F){6 $1w=4.h(\'l.2w\');5($1w.o==0)$1w=4.s(\'<l v="2w 3l"></l>\').h(\'l.2w\');p $1w.2u();5(2s)4.2v($1w)}6 t=4.11();6 j=t*4.w;6 1M=j+4.w-1;5(1M>=4.g.o)1M=4.g.o-1;6 $1a=4.h(\'1l.S\');$1a.h(\'z\').2T(8(i){6 $z=$(4);5(i>=j&&i<=1M){$z.2n()}p{$z.1L()}});4.2p=t;$1a.1b(\'3W\');b 4},2k:8(){b 2o.3X(4.g.o/4.w)},2v:8(C){6 9=4;6 O=4.2k();6 t=4.11();6 j=t*4.w;6 1d=4.1f-1;6 m=t-2o.3j((4.1f-1)/2)+1;5(m>0){6 2x=O-m;5(2x<1d){m=m-(1d-2x)}}5(m<0){m=0}5(t>0){6 1G=j-4.w;C.s(\'<a V="W" y="#\'+4.g[1G].f+\'" n="\'+4.23+\'">\'+4.23+\'</a>\')}5(m>0){4.1N(C,0,O);5(m>1)C.s(\'<x v="3m">&3n;</x>\');1d--}3Y(1d>0){4.1N(C,m,O);1d--;m++}5(m<O){6 2y=O-1;5(m<2y)C.s(\'<x v="3m">&3n;</x>\');4.1N(C,2y,O)}6 10=j+4.w;5(10<4.g.o){C.s(\'<a V="W" y="#\'+4.g[10].f+\'" n="\'+4.22+\'">\'+4.22+\'</a>\')}C.h(\'a\').1m(8(e){9.1i(e,4)});b 4},1N:8(C,m,O){6 1O=m+1;6 3o=4.11();5(m==3o)C.s(\'<x v="N">\'+1O+\'</x>\');p 5(m<O){6 3p=m*4.w;C.s(\'<a V="W" y="#\'+4.g[3p].f+\'" n="\'+1O+\'">\'+1O+\'</a>\')}b 4}});$.2N(4,2D,2M);5(4.U&&!$.3Z)4.U=k;5(4.1V)4.$2l=$(4.1V);5(4.1W)4.$1t=$(4.1W);5(4.1Y)4.$14=$(4.1Y);4.2S();5(4.1f<3)4.1f=3;4.2p=-1;4.D=4.g[0];6 9=4;5(4.$14)4.$14.1L();5(4.1X){4.$E=$(4.1X).2u();5(4.2G){5(4.24){4.$E.s(\'<l v="1p-M"><a y="#P" v="P" n="\'+4.1h+\'">\'+4.1h+\'</a></l>\')}p{4.$E.s(\'<l v="1p-M"><a y="#Z" v="Z" n="\'+4.1g+\'">\'+4.1g+\'</a></l>\')}4.$E.h(\'l.1p-M a\').1m(8(e){9.3b();e.K();b k})}5(4.2H){4.$E.s(\'<l v="1H-M"><a v="3f" V="W" n="\'+4.1Z+\'">\'+4.1Z+\'</a><a v="1c" V="W" n="\'+4.21+\'">\'+4.21+\'</a></l>\').h(\'l.1H-M a\').1m(8(e){9.1i(e,4)})}}6 2z=!4.U||!2A.f;5(4.U&&2A.f){6 f=$.F.1x(2A.f);6 c=17[f];5(!c)2z=r}5(2z)4.T(0,k,r);5(4.2J){$(41).42(8(e){6 3q=e.3r?e.3r:e.3s?e.3s:0;43(3q){15 32:9.1c();e.K();16;15 33:9.3d();e.K();16;15 34:9.10();e.K();16;15 35:9.T(9.g.o-1);e.K();16;15 36:9.T(0);e.K();16;15 37:9.1F();e.K();16;15 39:9.1c();e.K();16}})}5(4.24)4.Z();1o(8(){9.2V()},2L);b 4}})(44);', 62, 253, '||||this|if|var||function|gallery||return|imageData|index||hash|data|find||startIndex|false|div|pageNum|title|length|else|image|true|append|page|isSync|class|numThumbs|span|href|li|nextIndex|attr|pager|currentImage|controlsContainer|galleriffic|undefined|dontPause|bypassHistory|slideshowTimeout|preventDefault|currentIndex|controls|current|numPages|pause|listItem|position|thumbs|gotoIndex|enableHistory|rel|history|preloadStartIndex|getNextIndex|play|nextPage|getCurrentPage|previousCaption|transitionOutCallback|loadingContainer|case|break|allImages|isSlideshowRunning|caption|thumbsUl|removeClass|next|pagesRemaining|gotoImage|maxPagesToShow|playLinkText|pauseLinkText|clickHandler|aThumb|slideUrl|ul|click|preloadRecursive|setTimeout|ss|addClass|getDefaultTransitionDuration|previousSlide|captionContainer|newCaption|topPager|bottomPager|normalizeHash|preloadAhead|addImage|insert|remove|updateThumbs|preloadCount|prevIndex|previous|prevPage|nav|buildImage|fadeTo|newSlide|hide|stopIndex|buildPageLink|pageLabel|imageCounter|version|getImage|removeImageByHash|ownerGallery|delay|imageContainerSel|captionContainerSel|controlsContainerSel|loadingContainerSel|prevLinkText||nextLinkText|nextPageLinkText|prevPageLinkText|autoStart|defaultTransitionDuration|onSlideChange|onTransitionOut|onTransitionIn|onPageTransitionOut|onPageTransitionIn|onImageAdded|onImageRemoved|link|updateIndices|children|eq|preloadNext|getPrevIndex|ssAdvance|getNumPages|imageContainer|isTransitioning|show|Math|displayedPage|selected|postTransitionOutHandler|needsPagination|top|empty|buildPager|bottom|remainingPageCount|lastPageNum|initFirstImage|location|replace|removeImageByIndex|defaults|enableTopPager|enableBottomPager|renderSSControls|renderNavControls|Next|enableKeyboardNavigation|syncTransitions|1000|settings|extend|thumbExists|name|splice|removeImage|initializeThumbs|each|isPreloadComplete|preloadInit|preloadRelocate|new|Image|onload|alt|src|||||||clearTimeout||html|toggleSlideshow|lastPage|previousPage|refresh|prev|syncThumbs|css|opacity|floor|rebuildThumbs|pagination|ellipsis|hellip|currentPage|imageIndex|key|charCode|keyCode|3000|Play|Pause|Previous|rsaquo|lsaquo|Prev|fn|appendImage|insertImage|typeof|string|thumb|push|before|removeAttr|delete|for|500|100|historyLoad|String|end|relocatePreload|wrapper|advance|nbsp|filter|prepend|noscript|ceil|while|historyInit||document|keydown|switch|jQuery'.split('|'), 0, {}))
/**
* 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);


