Your IP : 216.73.216.43


Current Path : /home/cbequiperp/www/components/com_flexicontent/librairies/galleriffic/js/
Upload File :
Current File : /home/cbequiperp/www/components/com_flexicontent/librairies/galleriffic/js/jquery.galleriffic.js

/**
 * jQuery Galleriffic plugin
 *
 * Copyright (c) 2008 Trent Foley (https://trentacular.com)
 * Licensed under the MIT License:
 *   https://www.opensource.org/licenses/mit-license.php
 *
 * Much thanks to primary contributer Ponticlaro (https://www.ponticlaro.com)
 *
 * Modified by Jay Hayes (https://iamvery.com)
 * Modified by ggppdk (https:://flexicontent.org)
 */
;(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.1.0',

		// Strips HTML tags
		stripHtml: function(html) {
			var tmp = document.createElement("DIV");
			tmp.innerHTML = html;
			return tmp.textContent || tmp.innerText || "";
		},

		// 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 = {
		use_pages:                 true,
		delay:                     3000,
		numThumbs:                 20,
		slideHeight:               0,
		preloadAhead:              40, // Set to -1 to preload all images
		enableTopPager:            false,
		enableBottomPager:         true,
		maxPagesToShow:            7,
		imageContainerSel:         '',
		captionContainerSel:       '',
		ssControlsContainerSel:    '',
		navControlsContainerSel:   '',
		loadingContainerSel:       '',
		playLinkText:              'Play',
		pauseLinkText:             'Pause',
		prevLinkText:              'Previous',
		nextLinkText:              'Next',
		nextPageLinkText:          'Next ›',
		prevPageLinkText:          '‹ Prev',
		enableHistory:             false,
		enableFancybox:            false,
		fancyOptions:              {},
		enableKeyboardNavigation:  true,
		autoStart:                 false,
		syncTransitions:           true,
		defaultTransitionDuration: 1000,

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

		// accepts a delegate like such: function(slide, caption, isSync, callback) { ... }
		onTransitionOut:           function(slide, caption, isSync, callback)
		{
			slide.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0, callback);
			caption.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0);
		},

		// accepts a delegate like such: function(slide, caption, isSync) { ... }
		onTransitionIn:            function(slide, caption, isSync)
		{
			var duration = this.getDefaultTransitionDuration(isSync);
			slide.fadeTo(duration, 1.0);

			// Position the caption at the bottom of the image and set its opacity
			var slideImage = slide.find('img');

			var left = Math.ceil((slide.width() - slideImage.width()) / 2),
				offTop = slideImage.get(0).offsetTop,
				height = Math.floor( slideImage.outerHeight(true) ),
				bottom = Math.ceil(slide.height() - slideImage.outerHeight(true)),
				left   = Math.ceil((slide.width() - slideImage.width()) / 2),
				width  = slideImage.width() < slide.width() ? slideImage.width() : slide.width();

			//slide.closest('.slideshow-container').find('.nav-controls-box').find('a').css({'height': height});
			//slide.closest('.slideshow-container').find('.loader').css({'height': height});

			var caption_el = caption.get(0);
			caption_el.style.width = width + 'px';
			caption_el.style.display = 'block';
			caption_el.style.bottom = (bottom - offTop) + 'px';
			caption_el.style.left = left + 'px';
		},

		// accepts a delegate like such: function(callback) { ... }
		onPageTransitionOut:       function(callback)
		{
			this.fadeTo('fast', 0.0, callback);
		},

		// accepts a delegate like such: function() { ... }
		onPageTransitionIn:        function()
		{
			this.fadeTo('fast', 1.0);
		},

		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 width = $aThumb.attr('data-width');
				var height = $aThumb.attr('data-height');
				var $caption = $li.find('.caption').remove();
				var $fancy = $li.find('a.gf_fancybox');//.find('a.fancy').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,
					width:width,
					height:height,
					slideUrl:slideUrl,
					caption:$caption,
					fancy:$fancy,
					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;

				image.width  = imageData.width;
				image.height = imageData.height;
				image.style.maxWidth  = imageData.width + 'px';
				image.style.maxHeight = (this.slideHeight > imageData.height ? imageData.height + 'px' : '');
				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.$ssControlsContainer) {
					this.$ssControlsContainer
						.find('div.ss-controls a').removeClass().addClass('play')
						.attr('title', '')
					//.attr('title', $.galleriffic.stripHtml(this.playLinkText))
						.attr('href', '#play')
						.html(this.playLinkText);
				}

				return this;
			},

			// Plays the slideshow
			play: function() {
				this.isSlideshowRunning = true;

				if (this.$ssControlsContainer) {
					this.$ssControlsContainer
						.find('div.ss-controls a').removeClass().addClass('pause')
						.attr('title', '')
					//.attr('title', $.galleriffic.stripHtml(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)
					$.history.load(String(imageData.hash));  // At the moment, history.load 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;

				// Prevent reloading same image
				if (this.currentImage && this.currentImage.index == index)
					return this;

				if (this.onSlideChange && this.currentImage)
					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 touchDevice = ('ontouchstart' in document.documentElement);
				if (touchDevice && typeof window.orientation !== 'undefined')
				{
					if (typeof this.custom_styles === 'undefined' || !!!this.custom_styles)
					{
						this.custom_styles = document.createElement('style');
						document.head.appendChild(this.custom_styles);
					}

					if (window.orientation == 0)
					{
						this.custom_styles.innerHTML = '#gf_container_' + this.unique_id + ' div.slideshow-container, #gf_container_' + this.unique_id + ' span.image-wrapper a {max-height: 50vh; }';
					}
					else
					{
						this.custom_styles.innerHTML = '#gf_container_' + this.unique_id + ' div.slideshow-container, #gf_container_' + this.unique_id + ' span.image-wrapper a {max-height: 96vh; }';
					}
				}

				var imageData = this.currentImage;
				if (!imageData)
					return this;

				var index = imageData.index;

				// Update Controls
				if (this.$navControlsContainer) {
					this.$navControlsContainer
						.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.image-wrapper.current').addClass('previous').removeClass('current');
				var previousCaption = 0;

				if (this.$captionContainer) {
					previousCaption = this.$captionContainer.find('span.image-caption.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;

					// Hide the old slide
					previousSlide.hide();//.remove();

					// Hide old caption
					if (previousCaption)
						previousCaption.hide();//.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;

					image.width  = imageData.width;
					image.height = imageData.height;
					image.style.maxWidth  = imageData.width + 'px';
					image.style.maxHeight = (this.slideHeight > imageData.height ? imageData.height + 'px' : '');
				}

				// 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);

				// Check for already created slide
				var newSlide = this.$imageContainer.find('.index_' + imageData.index).removeClass('previous').addClass('current').css({display: 'block', opacity: 1});

				// Construct new hidden span for the image
				if (!newSlide.length)
				{
					newSlide = this.$imageContainer
						.append('<span class="image-wrapper current index_' + imageData.index + '"></span>')
						.find('span.image-wrapper.current').css('opacity', '0');

					// Prevent click if a drag was started (minor drags are not considered, see dragstart_margin parameter)
					var onClick = 'if (gf_gallery_' + this.unique_id + '.mSlider.isDragging) {event.preventDefault(); event.stopPropagation(); return false; }';

					//if (this.enableFancybox && imageData.fancy.attr('href'))
					if (this.enableFancybox && imageData.fancy)
					{
						imageData.fancy.detach().appendTo(newSlide).show();
						//newSlide.append('<a class="fancy-link" href="' + imageData.fancy.attr('href') + '" title="' + imageData.title + '" onclick="' + onClick + '"> </a>');
						//newSlide.find('a').fancybox(this.fancyOptions);
					}
					else
					{
						newSlide.append('<a class="advance-link" rel="history" href="#'+this.data[nextIndex].hash+'" title="'+imageData.title+'" onclick="' + onClick + '"> </a>');
						newSlide.find('a').click(function(e) {
							gallery.clickHandler(e, this);
						});
					}

					newSlide.find('a').prepend(imageData.image);
				}

				var newCaption = 0;
				if (this.$captionContainer)
				{
					newCaption = this.$captionContainer.find('.index_' + imageData.index).removeClass('previous').addClass('current').css({'display': 'block', 'opacity': 0});

					if (!newCaption.length)
					{
						// Construct new hidden caption for the image
						newCaption = this.$captionContainer
							.append('<span class="image-caption current index_' + imageData.index + '"></span>')
							.find('span.image-caption.current').css('opacity', '0')
							.append(imageData.caption);
					}

					newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);
					setTimeout(function() { newCaption.removeClass('transitioning'); }, this.getDefaultTransitionDuration(isSync));
				}

				// 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 $thumbsUl = this.find('ul.thumbs'),
					$thumbs     = $thumbsUl.children(),
					$thumbOld   = $thumbs.filter('.selected'),
					$thumbNew   = $thumbs.eq(this.currentImage.index),
					page        = this.getCurrentPage(),
					pBox        = $thumbsUl.parent().get(0);
					
				// Go to correct page
				if (page != this.displayedPage)
				{
					this.updateThumbs();
				}

				// Scroll to new image
				if (pBox.scrollWidth > pBox.clientWidth)
				{
					var elem_oLeft = $thumbNew.get(0).offsetLeft;
					var elem_width = $thumbNew.outerWidth(true);

					var elem_rightEdge = elem_oLeft + elem_width;
					var pBox_rightEdge = pBox.scrollLeft + pBox.clientWidth;

					//window.console.log('elem_oLeft: ' + elem_oLeft + ' - elem_width: ' + elem_width + ' - elem_rightEdge: ' + elem_rightEdge);
					//window.console.log('pBox.scrollLeft: ' + pBox.scrollLeft + ' - pBox.clientWidth: ' + pBox.clientWidth + ' - pBox_rightEdge: ' + pBox_rightEdge);

					var extra = 3 * elem_width / 4;
					if (pBox_rightEdge < elem_rightEdge + extra)
					{
						//pBox.scrollLeft = elem_rightEdge - pBox.clientWidth + extra;
						$(pBox).animate({ scrollLeft: (elem_rightEdge - pBox.clientWidth + extra) }, $thumbOld.length ? 600 : 0);
					}

					if (elem_oLeft < pBox.scrollLeft + extra)
					{
						//pBox.scrollLeft = elem_oLeft - extra;
						$(pBox).animate({ scrollLeft: (elem_oLeft - extra) }, $thumbOld.length ? 600 : 0);
					}
				}

				// Remove existing selected class and add selected class to new thumb
				$thumbOld.removeClass('selected');
				$thumbNew.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' + (this.enableTopPager == 2 ? ' gf_inline_nav' : '') + '"></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 $thumbsUl  = gallery.find('ul.thumbs');
				var $thumbs    = gallery.find('ul.thumbs li');

				// Find thumbs per page
				this.numThumbs = Math.floor( ($thumbsUl.width() - 1) / $thumbs.first().outerWidth(true) );
				// Balance thumbs in last page
				this.numThumbs = Math.ceil($thumbs.length / Math.ceil($thumbs.length / this.numThumbs));
				
				//window.console.log( ($thumbsUl.width() - 1) + ' - ' + $thumbs.first().outerWidth(true) + ' - ' + ( ($thumbsUl.width() - 1) / $thumbs.first().outerWidth(true) ) );

				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" class="prev_page_btn" href="#'+this.data[prevPage].hash+'" title="'+(this.enableTopPager != 2 ? this.prevPageLinkText : (page > 0 ? page : '-'))+'">'+this.prevPageLinkText+'</a>');
				}
				else {
					pager.append('<span class="ellipsis prev_page_btn">'+this.prevPageLinkText+'</span>');
				}

				if (this.enableTopPager != 2)
				{
					// 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);
					}
				}
				else
				{
					pager.append('<span class="gf_pagination_info">' + (page + 1) + '/' + numPages + '</span>');
				}


				// Next Page Link
				var nextPage = startIndex + this.numThumbs;
				if (nextPage < this.data.length) {
					pager.append('<a rel="history" class="next_page_btn" href="#'+this.data[nextPage].hash+'" title="'+(this.enableTopPager != 2 ? this.nextPageLinkText : (page < numPages ? page+2 : '-'))+'">'+this.nextPageLinkText+'</a>');
				}
				else {
					pager.append('<span class="ellipsis next_page_btn">'+this.nextPageLinkText+'</span>');
				}

				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 && !$.history)
			this.enableHistory = false;

		// Verify the fancybox plugin is available
		if (this.enableFancybox && !$.fancybox)
			this.enableFancybox = 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;
		var gallery = this;

		// Hide the loadingContainer
		if (this.$loadingContainer)
			this.$loadingContainer.hide();

		// Setup controls
		if (this.ssControlsContainerSel) {
			this.$ssControlsContainer = $(this.ssControlsContainerSel).empty();
			if (this.autoStart) {
				this.$ssControlsContainer
					.append('<div class="ss-controls"><a href="#pause" class="pause" title="'+this.pauseLinkText+'">'+this.pauseLinkText+'</a></div>');
			} else {
				this.$ssControlsContainer
					.append('<div class="ss-controls"><a href="#play" class="play" title="'+this.playLinkText+'">'+this.playLinkText+'</a></div>');
			}

			this.$ssControlsContainer.find('div.ss-controls a')
				.click(function(e) {
					gallery.toggleSlideshow();
					e.preventDefault();
					return false;
				});
		}

		if (this.navControlsContainerSel) {
			this.$navControlsContainer = $(this.navControlsContainerSel).empty();
			this.$navControlsContainer
				.append('<div class="nav-controls"><a class="prev" rel="history" data-title="'+this.prevLinkText+'"><div>'+this.prevLinkText+'</div></a><a class="next" rel="history" data-title="'+this.nextLinkText+'"><div>'+this.nextLinkText+'</div></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;
				}
			});
		}

		// Resize thumbnails container
		$(window).resize(function()
		{
			gallery.rebuildThumbs();
		});


		/**
		 * TOUCH events for CAROUSEL area
		 */

		var slider = {
			mode: 'horizontal',
			dragstart_margin: 20,
			dragwalk_margin: (this.use_pages ? 100 : 20),
			isDragging: false,
			items_box: this.find('ul.thumbs')
		};

		var boxPos = 0;
		var startPos = 0;
		var prop = slider.mode=='horizontal' ? 'left' : 'top';


		// Prevent click if a drag was started (minor drags are not considered, see dragstart_margin parameter)
		this.find('ul.thumbs li a img').on('click', function(ev)
		{
			// Click event is scheduled/queued after mouseup event, isDragging FLAG is cleared via setTimeout, thus we can use it here to prevent clicks
			if (slider.isDragging)
			{
				var e = ev.originalEvent;
				e.preventDefault();
				e.stopPropagation();
			}
		});

		var startEvents = 'mousedown touchstart';
		this.on(startEvents, function(ev)
		{
			var e = ev.originalEvent;  // Get original event
			if (ev.type=='mousedown')
			{
				// In the case of using mouse events, we need to prevent events for 'mousedown'
				e.preventDefault(); e.stopPropagation();  // this may create undesired effects in some cases
			}

			var obj = ev.type!='touchstart' ? e : e.changedTouches[0]; // reference first touch point for this event

			// Indicate draging by changing mouse cursor
			$(slider.items_box).css('cursor', 'pointer' );

			// Stop all animations and force them to complete, to get proper current position of the slider
			$(slider.items_box).finish();
			boxPos = parseInt($(slider.items_box).css(slider.mode=='horizontal' ? 'left' : 'top'));
			startPos = parseInt(slider.mode=='horizontal' ? obj.clientX : obj.clientY);
			//window.console.log ('Status: mousedown -- Start coordinate: ' + startPos + 'px');
		});


		var moveEvents = 'mousemove touchmove';
		this.on(moveEvents, function(ev)
		{
			var e = ev.originalEvent;  // Get original event
			//e.preventDefault();	//e.stopPropagation();

			var obj = ev.type!='touchmove' ? e : e.changedTouches[0]; // reference first touch point for this event
			var moveStarted = (startPos!=0);
			if (!moveStarted) return;

			var travelDist = parseInt(slider.mode=='horizontal' ? obj.clientX : obj.clientY) - startPos;
			//window.console.log ('Status: mousemove -- Distance traveled: ' + travelDist + 'px');

			// Check if drap distance is over the drag start threshold
			if (!slider.isDragging && Math.abs(travelDist) < slider.dragstart_margin) return;
			slider.isDragging = true;

			// Touch/Mouse Drag is at new point, retarget to new point,
			// Cancel all animations without completing them, to allow continuing from current position, thus avoiding position jump to previous touch position
			$(slider.items_box).stop(true, false);

			(slider.mode=='horizontal')
				? $(slider.items_box).animate({ left: boxPos+travelDist }, 'fast')
				: $(slider.items_box).animate({ top: boxPos+travelDist }, 'fast');
		});


		var endEvents = 'mouseleave mouseup touchend';
		this.on(endEvents, function(ev)
		{
			var e = ev.originalEvent;  // Get original event
			//e.preventDefault(); //e.stopPropagation();

			var obj = ev.type!='touchend' ? e : e.changedTouches[0]; // reference first touch point for this event

			var moveStarted = (startPos!=0);
			if (!moveStarted) return;  // check if initial click was not inside the slider
			var travelDist = parseInt(slider.mode=='horizontal' ? obj.clientX : obj.clientY) - startPos;
			//window.console.log ('Status: mouseup -- End coordinate: ' + (slider.mode=='horizontal' ? obj.clientX : obj.clientY) + 'px');

			$(slider.items_box).css('cursor', 'auto');  // restore mouse pointer

			// Check if drag distance is over the drag walk threshold, and walk the slider to proper direction,
			if (Math.abs(travelDist) > slider.dragwalk_margin)
			{
				//window.console.log('DO MOVE: ' + travelDist + ' ' + gallery.currentImage.index + ' ' + gallery.numThumbs);

				// Cancel all animations without completing them, to allow walking from current position, thus avoiding position jump to last touch position
				$(slider.items_box).stop(true, false);

				// Cancel autoplay, to avoid confusion to the user
				gallery.pause();

				if (this.use_pages)
				{
					var page = gallery.getCurrentPage();
					var do_move_next = ((travelDist < 0) && page < gallery.getNumPages() - 1);
					var do_move_prev = ((travelDist > 0) && page > 0);
					//window.console.log(page + ' ' + (gallery.getNumPages() - 1) + travelDist + ' ' + do_move_next + ' ' + do_move_prev);

					// Walk the slider
					if (do_move_next || do_move_prev)
					{
						// Goto next page but first hide items box to avoid potential ``flashing``
						$(slider.items_box).hide();
						(travelDist < 0) ? gallery.nextPage(true, true) : gallery.previousPage(true, true);

						$(slider.items_box).css(prop, travelDist < 0 ? '50%' : '-50%');

						// Consider delay in hiding currently selected item
						this.find('ul.thumbs li.selected').hide();
					}

					// If having pagination then move back to start
					setTimeout(function()
					{
						$(slider.items_box).show();
						var css = {};
						css[prop] = 0;
						$(slider.items_box).animate(css, 'fast');
					}, 200);
				}
				else
				{
					// Move thumbs container to start (we scrolled the parent of the thumbs container, not the thumbs container itself)
					$(slider.items_box).css(prop, 0);

					// We scrolled the parent of the thumbs container, not the thumbs container itself
					var node = $(slider.items_box).get(0).parentNode;

					//window.console.log ('scrollLeft (before): ' + $(node).scrollLeft() + ' scrollLeft New: ' + ($(node).scrollLeft() - travelDist));
					(slider.mode=='horizontal') ? (node.scrollLeft -= travelDist) : (node.scrollTop += travelDist);
					//window.console.log ('scrollLeft (after): ' + $(node).scrollLeft());
				}
			}

			else
			{
				//window.console.log('CANCEL MOVE: ' + travelDist);

				// Drag is under threshold, return slider to original position before dragging was stated,
				// But first cancel all animations without completing, to allow returning from current position, thus avoiding position jump to last touch position
				$(slider.items_box).stop(true, false);
				var css = {};
				css[prop] = boxPos;
				$(slider.items_box).animate(css, 'fast');
			}

			startPos = 0;
			setTimeout(function(){ slider.isDragging = false; }, 100);
		});



		/**
		 * MAIN AREA IMAGE
		 */
		var mSlider = {
			mode: 'horizontal',
			dragstart_margin: 20,
			dragwalk_margin: (this.use_pages ? 100 : 20),
			isDragging: false,
			items_box: $(this.imageContainerSel)
		};

		this.mSlider = mSlider;

		var mBoxPos = 0;
		var mStartPos = 0;
		var mProp = this.mSlider.mode=='horizontal' ? 'left' : 'top';


		var startEvents = 'mousedown touchstart';
		mSlider.items_box.on(startEvents, function(ev)
		{
			var e = ev.originalEvent;  // Get original event
			if (ev.type=='mousedown')
			{
				// In the case of using mouse events, we need to prevent events for 'mousedown'
				e.preventDefault(); e.stopPropagation();  // this may create undesired effects in some cases
			}

			var obj = ev.type!='touchstart' ? e : e.changedTouches[0]; // reference first touch point for this event

			// Indicate draging by changing mouse cursor
			//mSlider.items_box.css('cursor', (mSlider.mode=='horizontal' ? (mTravelDist<0 ? 'w-resize' : 'e-resize') : (mTravelDist<0 ? 'n-resize' : 's-resize')) );
			mSlider.items_box.css('cursor', 'pointer' );

			// Stop all animations and force them to complete, to get proper current position of the slider
			mSlider.items_box.finish();
			mBoxPos = parseInt(mSlider.items_box.css(mSlider.mode=='horizontal' ? 'left' : 'top'));
			mStartPos = parseInt(mSlider.mode=='horizontal' ? obj.clientX : obj.clientY);
			//window.console.log ('Status: mousedown -- Start coordinate: ' + mStartPos + 'px');
		});


		var moveEvents = 'mousemove touchmove';
		mSlider.items_box.on(moveEvents, function(ev)
		{
			var e = ev.originalEvent;  // Get original event
			//e.preventDefault();	//e.stopPropagation();

			var obj = ev.type!='touchmove' ? e : e.changedTouches[0]; // reference first touch point for this event
			var moveStarted = (mStartPos!=0);
			if (!moveStarted) return;

			var mTravelDist = parseInt(mSlider.mode=='horizontal' ? obj.clientX : obj.clientY) - mStartPos;
			//window.console.log ('Status: mousemove -- Distance traveled: ' + mTravelDist + 'px');

			// Check if drap distance is over the drag start threshold
			if (!mSlider.isDragging && Math.abs(mTravelDist) < mSlider.dragstart_margin) return;
			mSlider.isDragging = true;
			//window.console.log ('Dragging');

			// Touch/Mouse Drag is at new point, retarget to new point,
			// Cancel all animations without completing them, to allow continuing from current position, thus avoiding position jump to previous touch position
			mSlider.items_box.stop(true, false);

			(mSlider.mode=='horizontal')
				? mSlider.items_box.animate({ left: mBoxPos+mTravelDist }, 'fast')
				: mSlider.items_box.animate({ top: mBoxPos+mTravelDist }, 'fast');
		});


		var endEvents = 'mouseleave mouseup touchend';
		mSlider.items_box.on(endEvents, function(ev)
		{
			var e = ev.originalEvent;  // Get original event
			//e.preventDefault(); //e.stopPropagation();

			var obj = ev.type!='touchend' ? e : e.changedTouches[0]; // reference first touch point for this event

			var moveStarted = (mStartPos!=0);
			if (!moveStarted) return;  // check if initial click was not inside the slider
			var mTravelDist = parseInt(mSlider.mode=='horizontal' ? obj.clientX : obj.clientY) - mStartPos;
			//window.console.log ('Status: mouseup -- End coordinate: ' + (mSlider.mode=='horizontal' ? obj.clientX : obj.clientY) + 'px');

			mSlider.items_box.css('cursor', 'auto');  // restore mouse pointer

			// Check if drag distance is over the drag walk threshold, and walk the slider to proper direction,
			if (Math.abs(mTravelDist) > mSlider.dragwalk_margin)
			{
				//window.console.log('DO main MOVE: ' + mTravelDist + ' ' + gallery.currentImage.index + ' ' + gallery.numThumbs);

				// Cancel all animations without completing them, to allow walking from current position, thus avoiding position jump to last touch position
				mSlider.items_box.stop(true, false);

				// Cancel autoplay, to avoid confusion to the user
				gallery.pause();

				if (1)
				{
					// Move container to start (we scrolled it but now need it back to proper position for moving to next image)
					var css = {};
					css[mProp] = 0;
					mSlider.items_box.animate(css, 'slow');

					// Go to next / previous image
					(mTravelDist < 0) ? gallery.next() : gallery.previous();
				}
			}

			else
			{
				//window.console.log('CANCEL main MOVE: ' + mTravelDist);

				// Drag is under threshold, return slider to original position before dragging was stated,
				// But first cancel all animations without completing, to allow returning from current position, thus avoiding position jump to last touch position
				mSlider.items_box.stop(true, false);
				var css = {};
				css[mProp] = mBoxPos;
				mSlider.items_box.animate(css, 'fast');
			}

			mStartPos = 0;
			setTimeout(function(){ mSlider.isDragging = false; }, 100);
		});



		// Auto start the slideshow
		if (this.autoStart)
			this.play();

		// Kickoff Image Preloader after 1 second
		setTimeout(function() { gallery.preloadInit(); }, 1000);

		return this;
	};
})(jQuery);