PageSourceSearch

https://www.mediainfocus.com/Portals/MediaInfocus/Skins/BD003-YellowGreen/slider/jquery.sliderPro.js?cdv=1718

js mediainfocus.com collected 2026-10-08 06:10:44 UTC 196,414 bytes, 5,904 lines download raw bytes

vendor: 19,326 bytes, lines 1-516
1/*!
2*  - v1.1.0
3* Homepage: http://bqworks.com/slider-pro/
4* Author: bqworks
5* Author URL: http://bqworks.com/
6*/
7;(function( window, $ ) {
8
9	"use strict";
10
11	// Static methods for Slider Pro
12	$.SliderPro = {
13
14		// List of added modules
15		modules: [],
16
17		// Add a module by extending the core prototype
18		addModule: function( name, module ) {
19			this.modules.push( name );
20			$.extend( SliderPro.prototype, module );
21		}
22	};
23
24	// namespace
25	var NS = $.SliderPro.namespace = 'SliderPro';
26
27	var SliderPro = function( instance, options ) {
28
29		// Reference to the slider instance
30		this.instance = instance;
31
32		// Reference to the slider jQuery element
33		this.$slider = $( this.instance );
34
35		// Reference to the slides (sp-slides) jQuery element
36		this.$slides = null;
37
38		// Reference to the mask (sp-mask) jQuery element
39		this.$slidesMask = null;
40
41		// Reference to the slides (sp-slides-container) jQuery element
42		this.$slidesContainer = null;
43
44		// Array of SliderProSlide objects, ordered by their DOM index
45		this.slides = [];
46
47		// Array of SliderProSlide objects, ordered by their left/top position in the slider.
48		// This will be updated continuously if the slider is loopable.
49		this.slidesOrder = [];
50
51		// Holds the options passed to the slider when it was instantiated
52		this.options = options;
53
54		// Holds the final settings of the slider after merging the specified
55		// ones with the default ones.
56		this.settings = {};
57
58		// Another reference to the settings which will not be altered by breakpoints or by other means
59		this.originalSettings = {};
60
61		// Reference to the original 'gotoSlide' method
62		this.originalGotoSlide = null;
63
64		// The index of the currently selected slide (starts with 0)
65		this.selectedSlideIndex = 0;
66
67		// The index of the previously selected slide
68		this.previousSlideIndex = 0;
69
70		// Indicates the position of the slide considered to be in the middle.
71		// If there are 5 slides (0, 1, 2, 3, 4) the middle position will be 2.
72		// If there are 6 slides (0, 1, 2, 3, 4, 5) the middle position will be approximated to 2.
73		this.middleSlidePosition = 0;
74
75		// Indicates the type of supported transition (CSS3 2D, CSS3 3D or JavaScript)
76		this.supportedAnimation = null;
77
78		// Indicates the required vendor prefix for CSS (i.e., -webkit, -moz, etc.)
79		this.vendorPrefix = null;
80
81		// Indicates the name of the CSS transition's complete event (i.e., transitionend, webkitTransitionEnd, etc.)
82		this.transitionEvent = null;
83
84		// Indicates the 'left' or 'top' position
85		this.positionProperty = null;
86
87		// Indicates if the current browser is IE
88		this.isIE = null;
89
90		// The position of the slides container
91		this.slidesPosition = 0;
92
93		// The width of the individual slide
94		this.slideWidth = 0;
95
96		// The height of the individual slide
97		this.slideHeight = 0;
98
99		// The width or height, depending on the orientation, of the individual slide
100		this.slideSize = 0;
101
102		// Reference to the old slide width, used to check if the width has changed
103		this.previousSlideWidth = 0;
104
105		// Reference to the old slide height, used to check if the height has changed
106		this.previousSlideHeight = 0;
107		
108		// Reference to the old window width, used to check if the window width has changed
109		this.previousWindowWidth = 0;
110		
111		// Reference to the old window height, used to check if the window height has changed
112		this.previousWindowHeight = 0;
113
114		// The distance from the margin of the slider to the left/top of the selected slide.
115		// This is useful in calculating the position of the selected slide when there are 
116		// more visible slides.
117		this.visibleOffset = 0;
118
119		// Property used for deferring the resizing of the slider
120		this.allowResize = true;
121
122		// Unique ID to be used for event listening
123		this.uniqueId = new Date().valueOf();
124
125		// Stores size breakpoints
126		this.breakpoints = [];
127
128		// Indicates the current size breakpoint
129		this.currentBreakpoint = -1;
130
131		// An array of shuffled indexes, based on which the slides will be shuffled
132		this.shuffledIndexes = [];
133
134		// Initialize the slider
135		this._init();
136	};
137
138	SliderPro.prototype = {
139
140		// The starting place for the slider
141		_init: function() {
142			var that = this;
143
144			this.supportedAnimation = SliderProUtils.getSupportedAnimation();
145			this.vendorPrefix = SliderProUtils.getVendorPrefix();
146			this.transitionEvent = SliderProUtils.getTransitionEvent();
147			this.isIE = SliderProUtils.checkIE();
148
149			// Remove the 'sp-no-js' when the slider's JavaScript code starts running
150			this.$slider.removeClass( 'sp-no-js' );
151
152			// Add the 'ios' class if it's an iOS device
153			if ( window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ) {
154				this.$slider.addClass( 'ios' );
155			}
156
157			// Check if IE (older than 11) is used and add the version number as a class to the slider since
158			// older IE versions might need CSS tweaks.
159			var rmsie = /(msie) ([\w.]+)/,
160				ieVersion = rmsie.exec( window.navigator.userAgent.toLowerCase() );
161			
162			if ( this.isIE ) {
163				this.$slider.addClass( 'ie' );
164			}
165
166			if ( ieVersion !== null ) {
167				this.$slider.addClass( 'ie' + parseInt( ieVersion[2], 10 ) );
168			}
169
170			// Set up the slides containers
171			// slider-pro > sp-slides-container > sp-mask > sp-slides > sp-slide
172			this.$slidesContainer = $( '<div class="sp-slides-container"></div>' ).appendTo( this.$slider );
173			this.$slidesMask = $( '<div class="sp-mask"></div>' ).appendTo( this.$slidesContainer );
174			this.$slides = this.$slider.find( '.sp-slides' ).appendTo( this.$slidesMask );
175			this.$slider.find( '.sp-slide' ).appendTo( this.$slides );
176			
177			var modules = $.SliderPro.modules;
178
179			// Merge the modules' default settings with the core's default settings
180			if ( typeof modules !== 'undefined' ) {
181				for ( var i = 0; i < modules.length; i++ ) {
182					var defaults = modules[ i ].substring( 0, 1 ).toLowerCase() + modules[ i ].substring( 1 ) + 'Defaults';
183
184					if ( typeof this[ defaults ] !== 'undefined' ) {
185						$.extend( this.defaults, this[ defaults ] );
186					}
187				}
188			}
189
190			// Merge the specified setting with the default ones
191			this.settings = $.extend( {}, this.defaults, this.options );
192
193			// Initialize the modules
194			if ( typeof modules !== 'undefined' ) {
195				for ( var j = 0; j < modules.length; j++ ) {
196					if ( typeof this[ 'init' + modules[ j ] ] !== 'undefined' ) {
197						this[ 'init' + modules[ j ] ]();
198					}
199				}
200			}
201
202			// Keep a reference of the original settings and use it
203			// to restore the settings when the breakpoints are used.
204			this.originalSettings = $.extend( {}, this.settings );
205
206			// Get the reference to the 'gotoSlide' method
207			this.originalGotoSlide = this.gotoSlide;
208
209			// Parse the breakpoints object and store the values into an array,
210			// sorting them in ascending order based on the specified size.
211			if ( this.settings.breakpoints !== null ) {
212				for ( var sizes in this.settings.breakpoints ) {
213					this.breakpoints.push({ size: parseInt( sizes, 10 ), properties:this.settings.breakpoints[ sizes ] });
214				}
215
216				this.breakpoints = this.breakpoints.sort(function( a, b ) {
217					return a.size >= b.size ? 1: -1;
218				});
219			}
220
221			// Set which slide should be selected initially
222			this.selectedSlideIndex = this.settings.startSlide;
223
224			// Shuffle/randomize the slides
225			if ( this.settings.shuffle === true ) {
226				var slides = this.$slides.find( '.sp-slide' ),
227					shuffledSlides = [];
228
229				// Populate the 'shuffledIndexes' with index numbers
230				slides.each(function( index ) {
231					that.shuffledIndexes.push( index );
232				});
233
234				for ( var k = this.shuffledIndexes.length - 1; k > 0; k-- ) {
235					var l = Math.floor( Math.random() * ( k + 1 ) ),
236						temp = this.shuffledIndexes[ k ];
237
238					this.shuffledIndexes[ k ] = this.shuffledIndexes[ l ];
239					this.shuffledIndexes[ l ] = temp;
240				}
241
242				// Reposition the slides based on the order of the indexes in the
243				// 'shuffledIndexes' array
244				$.each( this.shuffledIndexes, function( index, element ) {
245					shuffledSlides.push( slides[ element ] );
246				});
247				
248				// Append the sorted slides to the slider
249				this.$slides.empty().append( shuffledSlides ) ;
250			}
251			
252			// Resize the slider when the browser window resizes.
253			// Also, deffer the resizing in order to not allow multiple
254			// resizes in a 200 milliseconds interval.
255			$( window ).on( 'resize.' + this.uniqueId + '.' + NS, function() {
256			
257				// Get the current width and height of the window
258				var newWindowWidth = $( window ).width(),
259					newWindowHeight = $( window ).height();
260				
261				// If the resize is not allowed yet or if the window size hasn't changed (this needs to be verified
262				// because in IE8 and lower the resize event is triggered whenever an element from the page changes
263				// its size) return early.
264				if ( that.allowResize === false ||
265					( that.previousWindowWidth === newWindowWidth && that.previousWindowHeight === newWindowHeight ) ) {
266					return;
267				}
268				
269				// Asign the new values for the window width and height
270				that.previousWindowWidth = newWindowWidth;
271				that.previousWindowHeight = newWindowHeight;
272			
273				that.allowResize = false;
274
275				setTimeout(function() {
276					that.resize();
277					that.allowResize = true;
278				}, 200 );
279			});
280
281			// Resize the slider when the 'update' method is called.
282			this.on( 'update.' + NS, function() {
283
284				// Reset the previous slide width
285				that.previousSlideWidth = 0;
286
287				// Some updates might require a resize
288				that.resize();
289			});
290
291			this.update();
292
293			// Fire the 'init' event
294			this.trigger({ type: 'init' });
295			if ( $.isFunction( this.settings.init ) ) {
296				this.settings.init.call( this, { type: 'init' });
297			}
298		},
299
300		// Update the slider by checking for setting changes and for slides
301		// that weren't initialized yet.
302		update: function() {
303			var that = this;
304
305			// Check the current slider orientation and reset CSS that might have been
306			// added for a different orientation, since the orientation can be changed
307			// at runtime.
308			if ( this.settings.orientation === 'horizontal' ) {
309				this.$slider.removeClass( 'sp-vertical' ).addClass( 'sp-horizontal' );
310				this.$slider.css({ 'height': '', 'max-height': '' });
311				this.$slides.find( '.sp-slide' ).css( 'top', '' );
312			} else if ( this.settings.orientation === 'vertical' ) {
313				this.$slider.removeClass( 'sp-horizontal' ).addClass( 'sp-vertical' );
314				this.$slides.find( '.sp-slide' ).css( 'left', '' );
315			}
316
317			// Set the position that will be used to arrange elements, like the slides,
318			// based on the orientation.
319			this.positionProperty = this.settings.orientation === 'horizontal' ? 'left' : 'top';
320
321			// Reset the 'gotoSlide' method
322			this.gotoSlide = this.originalGotoSlide;
323
324			// Loop through the array of SliderProSlide objects and if a stored slide is found
325			// which is not in the DOM anymore, destroy that slide.
326			for ( var i = this.slides.length - 1; i >= 0; i-- ) {
327				if ( this.$slider.find( '.sp-slide[data-index="' + i + '"]' ).length === 0 ) {
328					var slide = this.slides[ i ];
329
330					slide.destroy();
331					this.slides.splice( i, 1 );
332				}
333			}
334
335			this.slidesOrder.length = 0;
336
337			// Loop through the list of slides and initialize newly added slides if any,
338			// and reset the index of each slide.
339			this.$slider.find( '.sp-slide' ).each(function( index ) {
340				var $slide = $( this );
341
342				if ( typeof $slide.attr( 'data-init' ) === 'undefined' ) {
343					that._createSlide( index, $slide );
344				} else {
345					that.slides[ index ].setIndex( index );
346				}
347
348				that.slidesOrder.push( index );
349			});
350
351			// Calculate the position/index of the middle slide
352			this.middleSlidePosition = parseInt( ( that.slidesOrder.length - 1 ) / 2, 10 );
353
354			// Arrange the slides in a loop
355			if ( this.settings.loop === true ) {
356				this._updateSlidesOrder();
357			}
358
359			// Fire the 'update' event
360			this.trigger({ type: 'update' });
361			if ( $.isFunction( this.settings.update ) ) {
362				this.settings.update.call( this, { type: 'update' } );
363			}
364		},
365
366		// Create a SliderProSlide instance for the slide passed as a jQuery element
367		_createSlide: function( index, element ) {
368			var that = this,
369				slide = new SliderProSlide( $( element ), index, this.settings );
370
371			this.slides.splice( index, 0, slide );
372		},
373
374		// Arrange the slide elements in a loop inside the 'slidesOrder' array
375		_updateSlidesOrder: function() {
376			var	slicedItems,
377				i,
378
379				// Calculate the distance between the selected element and the middle position
380				distance = $.inArray( this.selectedSlideIndex, this.slidesOrder ) - this.middleSlidePosition;
381
382			// If the distance is negative it means that the selected slider is before the middle position, so
383			// slides from the end of the array will be added at the beginning, in order to shift the selected slide
384			// forward.
385			// 
386			// If the distance is positive, slides from the beginning of the array will be added at the end.
387			if ( distance < 0 ) {
388				slicedItems = this.slidesOrder.splice( distance, Math.abs( distance ) );
389
390				for ( i = slicedItems.length - 1; i >= 0; i-- ) {
391					this.slidesOrder.unshift( slicedItems[ i ] );
392				}
393			} else if ( distance > 0 ) {
394				slicedItems = this.slidesOrder.splice( 0, distance );
395
396				for ( i = 0; i <= slicedItems.length - 1; i++ ) {
397					this.slidesOrder.push( slicedItems[ i ] );
398				}
399			}
400		},
401
402		// Set the left/top position of the slides based on their position in the 'slidesOrder' array
403		_updateSlidesPosition: function() {
404			var selectedSlidePixelPosition = parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 );
405
406			for ( var slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) {
407				var slide = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ slideIndex ] );
408				slide.css( this.positionProperty, selectedSlidePixelPosition + ( slideIndex - this.middleSlidePosition  ) * ( this.slideSize + this.settings.slideDistance ) );
409			}
410		},
411
412		// Set the left/top position of the slides based on their position in the 'slidesOrder' array,
413		// and also set the position of the slides container.
414		_resetSlidesPosition: function() {
415			for ( var slideIndex = 0; slideIndex < this.slidesOrder.length; slideIndex++ ) {
416				var slide = this.$slides.find( '.sp-slide' ).eq( this.slidesOrder[ slideIndex ] );
417				slide.css( this.positionProperty, slideIndex * ( this.slideSize + this.settings.slideDistance ) );
418			}
419
420			var newSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + this.visibleOffset;
421			this._moveTo( newSlidesPosition, true );
422		},
423
424		// Called when the slider needs to resize
425		resize: function() {
426			var that = this;
427
428			// Check if the current window width is bigger than the biggest breakpoint
429			// and if necessary reset the properties to the original settings.
430			// 
431			// If the window width is smaller than a certain breakpoint, apply the settings specified
432			// for that breakpoint but only after merging them with the original settings
433			// in order to make sure that only the specified settings for the breakpoint are applied
434			if ( this.settings.breakpoints !== null && this.breakpoints.length > 0 ) {
435				if ( $( window ).width() > this.breakpoints[ this.breakpoints.length - 1 ].size && this.currentBreakpoint !== -1 ) {
436					this.currentBreakpoint = -1;
437					this._setProperties( this.originalSettings, false );
438				} else {
439					for ( var i = 0, n = this.breakpoints.length; i < n; i++ ) {
440						if ( $( window ).width() <= this.breakpoints[ i ].size ) {
441							if ( this.currentBreakpoint !== this.breakpoints[ i ].size ) {
442								var eventObject = { type: 'breakpointReach', size: this.breakpoints[ i ].size, settings: this.breakpoints[ i ].properties };
443								this.trigger( eventObject );
444								if ( $.isFunction( this.settings.breakpointReach ) )
445									this.settings.breakpointReach.call( this, eventObject );
446
447								this.currentBreakpoint = this.breakpoints[ i ].size;
448								var settings = $.extend( {}, this.originalSettings, this.breakpoints[ i ].properties );
449								this._setProperties( settings, false );
450								
451								return;
452							}
453
454							break;
455						}
456					}
457				}
458			}
459
460			// Set the width of the main slider container based on whether or not the slider is responsive,
461			// full width or full size
462			if ( this.settings.responsive === true ) {
463				if ( ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) &&
464					( this.settings.visibleSize === 'auto' || this.settings.visibleSize !== 'auto' && this.settings.orientation === 'vertical' )
465				) {
466					this.$slider.css( 'margin', 0 );
467					this.$slider.css({ 'width': $( window ).width(), 'max-width': '', 'marginLeft': - this.$slider.offset().left });
468				} else {
469					this.$slider.css({ 'width': '100%', 'max-width': this.settings.width, 'marginLeft': '' });
470				}
471			} else {
472				this.$slider.css({ 'width': this.settings.width });
473			}
474
475			// Calculate the aspect ratio of the slider
476			if ( this.settings.aspectRatio === -1 ) {
477				this.settings.aspectRatio = this.settings.width / this.settings.height;
478			}
479			
480			// Initially set the slide width to the size of the slider.
481			// Later, this will be set to less if there are multiple visible slides.
482			this.slideWidth = this.$slider.width();
483
484			// Set the height to the same size as the browser window if the slider is set to be 'fullWindow',
485			// or calculate the height based on the width and the aspect ratio.
486			if ( this.settings.forceSize === 'fullWindow' ) {
487				this.slideHeight = $( window ).height();
488			} else {
489				this.slideHeight = isNaN( this.settings.aspectRatio ) ? this.settings.height : this.slideWidth / this.settings.aspectRatio;
490			}
491
492			// Resize the slider only if the size of the slider has changed
493			// If it hasn't, return.
494			if ( this.previousSlideWidth !== this.slideWidth ||
495				this.previousSlideHeight !== this.slideHeight ||
496				this.settings.visibleSize !== 'auto' ||
497				this.$slider.outerWidth() > this.$slider.parent().width() ||
498				this.$slider.width() !== this.$slidesMask.width()
499			) {
500				this.previousSlideWidth = this.slideWidth;
501				this.previousSlideHeight = this.slideHeight;
502			} else {
503				return;
504			}
505
506			// The slide width or slide height is needed for several calculation, so create a reference to it
507			// based on the current orientation.
508			this.slideSize = this.settings.orientation === 'horizontal' ? this.slideWidth : this.slideHeight;
509			
510			// Initially set the visible size of the slides and the offset of the selected slide as if there is only
511			// on visible slide.
512			// If there will be multiple visible slides (when 'visibleSize' is different than 'auto'), these will
513			// be updated accordingly.
514			this.visibleSlidesSize = this.slideSize;
515			this.visibleOffset = 0;
516
517			// Loop through the existing slides and reset their size.
518			$.each( this.slides, function( index, element ) {
519				element.setSize( that.slideWidth, that.slideHeight );
520			});
521
522			// Set the initial size of the mask container to the size of an individual slide
523			this.$slidesMask.css({ 'width': this.slideWidth, 'height': this.slideHeight });
524
525			// Adjust the height if it's set to 'auto'
526			if ( this.settings.autoHeight === true ) {
527
528				// Delay the resizing of the height to allow for other resize handlers
529				// to execute first before calculating the final height of the slide
530				setTimeout( function() {
531					that._resizeHeight();
532				}, 1 );
533			} else {
534				this.$slidesMask.css( this.vendorPrefix + 'transition', '' );
535			}
536
537			// The 'visibleSize' option can be set to fixed or percentage size to make more slides
538			// visible at a time.
539			// By default it's set to 'auto'.
540			if ( this.settings.visibleSize !== 'auto' ) {
541				if ( this.settings.orientation === 'horizontal' ) {
542
543					// If the size is forced to full width or full window, the 'visibleSize' option will be
544					// ignored and the slider will become as wide as the browser window.
545					if ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) {
546						this.$slider.css( 'margin', 0 );
547						this.$slider.css({ 'width': $( window ).width(), 'max-width': '', 'marginLeft': - this.$slider.offset().left });
548					} else {
549						this.$slider.css({ 'width': this.settings.visibleSize, 'max-width': '100%', 'marginLeft': 0 });
550					}
551					
552					this.$slidesMask.css( 'width', this.$slider.width() );
553
554					this.visibleSlidesSize = this.$slidesMask.width();
555					this.visibleOffset = Math.round( ( this.$slider.width() - this.slideWidth ) / 2 );
556				} else {
557
558					// If the size is forced to full window, the 'visibleSize' option will be
559					// ignored and the slider will become as high as the browser window.
560					if ( this.settings.forceSize === 'fullWindow' ) {
561						this.$slider.css({ 'height': $( window ).height(), 'max-height': '' });
562					} else {
563						this.$slider.css({ 'height': this.settings.visibleSize, 'max-height': '100%' });
564					}
565
566					this.$slidesMask.css( 'height', this.$slider.height() );
567
568					this.visibleSlidesSize = this.$slidesMask.height();
569					this.visibleOffset = Math.round( ( this.$slider.height() - this.slideHeight ) / 2 );
570				}
571			}
572
573			this._resetSlidesPosition();
574
575			// Fire the 'sliderResize' event
576			this.trigger({ type: 'sliderResize' });
577			if ( $.isFunction( this.settings.sliderResize ) ) {
578				this.settings.sliderResize.call( this, { type: 'sliderResize' });
579			}
580		},
581
582		// Resize the height of the slider to the height of the selected slide.
583		// It's used when the 'autoHeight' option is set to 'true'.
584		_resizeHeight: function() {
585			var that = this,
586				selectedSlide = this.getSlideAt( this.selectedSlideIndex ),
587				size = selectedSlide.getSize();
588
589			selectedSlide.off( 'imagesLoaded.' + NS );
590			selectedSlide.on( 'imagesLoaded.' + NS, function( event ) {
591				if ( event.index === that.selectedSlideIndex ) {
592					var size = selectedSlide.getSize();
593					that._resizeHeightTo( size.height );
594				}
595			});
596
597			// If the selected slide contains images which are still loading,
598			// wait for the loading to complete and then request the size again.
599			if ( size !== 'loading' ) {
600				this._resizeHeightTo( size.height );
601			}
602		},
603
604		// Open the slide at the specified index
605		gotoSlide: function( index ) {
606			if ( index === this.selectedSlideIndex || typeof this.slides[ index ] === 'undefined' ) {
607				return;
608			}
609
610			var that = this;
611
612			this.previousSlideIndex = this.selectedSlideIndex;
613			this.selectedSlideIndex = index;
614
615			// Re-assign the 'as-selected' class to the currently selected slide
616			this.$slides.find( '.sp-selected' ).removeClass( 'sp-selected' );
617			this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected' );
618
619			// If the slider is loopable reorder the slides to have the selected slide in the middle
620			// and update the slides' position.
621			if ( this.settings.loop === true ) {
622				this._updateSlidesOrder();
623				this._updateSlidesPosition();
624			}
625
626			// Adjust the height of the slider
627			if ( this.settings.autoHeight === true ) {
628				this._resizeHeight();
629			}
630
631			// Calculate the new position that the slides container need to take
632			var newSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + this.visibleOffset;
633
634			// Move the slides container to the new position
635			this._moveTo( newSlidesPosition, false, function() {
636				if ( that.settings.loop === true ) {
637					that._resetSlidesPosition();
638				}
639
vendor: 11,591 bytes, lines 640-1002
640				// Fire the 'gotoSlideComplete' event
641				that.trigger({ type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex });
642				if ( $.isFunction( that.settings.gotoSlideComplete ) ) {
643					that.settings.gotoSlideComplete.call( that, { type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex } );
644				}
645			});
646
647			// Fire the 'gotoSlide' event
648			this.trigger({ type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex });
649			if ( $.isFunction( this.settings.gotoSlide ) ) {
650				this.settings.gotoSlide.call( this, { type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex } );
651			}
652		},
653
654		// Open the next slide
655		nextSlide: function() {
656			var index = ( this.selectedSlideIndex >= this.getTotalSlides() - 1 ) ? 0 : ( this.selectedSlideIndex + 1 );
657			this.gotoSlide( index );
658		},
659
660		// Open the previous slide
661		previousSlide: function() {
662			var index = this.selectedSlideIndex <= 0 ? ( this.getTotalSlides() - 1 ) : ( this.selectedSlideIndex - 1 );
663			this.gotoSlide( index );
664		},
665
666		// Move the slides container to the specified position.
667		// The movement can be instant or animated.
668		_moveTo: function( position, instant, callback ) {
669			var that = this,
670				css = {};
671
672			if ( position === this.slidesPosition ) {
673				return;
674			}
675			
676			this.slidesPosition = position;
677			
678			if ( ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) && this.isIE === false ) {
679				var transition,
680					left = this.settings.orientation === 'horizontal' ? position : 0,
681					top = this.settings.orientation === 'horizontal' ? 0 : position;
682
683				if ( this.supportedAnimation === 'css-3d' ) {
684					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
685				} else {
686					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
687				}
688
689				if ( typeof instant !== 'undefined' && instant === true ) {
690					transition = '';
691				} else {
692					this.$slides.addClass( 'sp-animated' );
693					transition = this.vendorPrefix + 'transform ' + this.settings.slideAnimationDuration / 1000 + 's';
694
695					this.$slides.on( this.transitionEvent, function( event ) {
696						if ( event.target !== event.currentTarget ) {
697							return;
698						}
699
700						that.$slides.off( that.transitionEvent );
701						that.$slides.removeClass( 'sp-animated' );
702						
703						if ( typeof callback === 'function' ) {
704							callback();
705						}
706					});
707				}
708
709				css[ this.vendorPrefix + 'transition' ] = transition;
710
711				this.$slides.css( css );
712			} else {
713				css[ 'margin-' + this.positionProperty ] = position;
714
715				if ( typeof instant !== 'undefined' && instant === true ) {
716					this.$slides.css( css );
717				} else {
718					this.$slides.addClass( 'sp-animated' );
719					this.$slides.animate( css, this.settings.slideAnimationDuration, function() {
720						that.$slides.removeClass( 'sp-animated' );
721
722						if ( typeof callback === 'function' ) {
723							callback();
724						}
725					});
726				}
727			}
728		},
729
730		// Stop the movement of the slides
731		_stopMovement: function() {
732			var css = {};
733
734			if ( ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) && this.isIE === false) {
735
736				// Get the current position of the slides by parsing the 'transform' property
737				var	matrixString = this.$slides.css( this.vendorPrefix + 'transform' ),
738					matrixType = matrixString.indexOf( 'matrix3d' ) !== -1 ? 'matrix3d' : 'matrix',
739					matrixArray = matrixString.replace( matrixType, '' ).match( /-?[0-9\.]+/g ),
740					left = matrixType === 'matrix3d' ? parseInt( matrixArray[ 12 ], 10 ) : parseInt( matrixArray[ 4 ], 10 ),
741					top = matrixType === 'matrix3d' ? parseInt( matrixArray[ 13 ], 10 ) : parseInt( matrixArray[ 5 ], 10 );
742					
743				// Set the transform property to the value that the transform had when the function was called
744				if ( this.supportedAnimation === 'css-3d' ) {
745					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
746				} else {
747					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
748				}
749
750				css[ this.vendorPrefix + 'transition' ] = '';
751
752				this.$slides.css( css );
753				this.$slides.off( this.transitionEvent );
754				this.slidesPosition = this.settings.orientation === 'horizontal' ? left : top;
755			} else {
756				this.$slides.stop();
757				this.slidesPosition = parseInt( this.$slides.css( 'margin-' + this.positionProperty ), 10 );
758			}
759
760			this.$slides.removeClass( 'sp-animated' );
761		},
762
763		// Resize the height of the slider to the specified value
764		_resizeHeightTo: function( height ) {
765			var css = { 'height': height };
766
767			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
768				css[ this.vendorPrefix + 'transition' ] = 'height ' + this.settings.heightAnimationDuration / 1000 + 's';
769				this.$slidesMask.css( css );
770			} else {
771				this.$slidesMask.animate( css, this.settings.heightAnimationDuration );
772			}
773		},
774
775		// Destroy the slider instance
776		destroy: function() {
777			// Remove the stored reference to this instance
778			this.$slider.removeData( 'sliderPro' );
779			
780			// Clean the CSS
781			this.$slider.removeAttr( 'style' );
782			this.$slides.removeAttr( 'style' );
783
784			// Remove event listeners
785			this.off( 'update.' + NS );
786			$( window ).off( 'resize.' + this.uniqueId + '.' + NS );
787
788			// Destroy modules
789			var modules = $.SliderPro.modules;
790
791			if ( typeof modules !== 'undefined' ) {
792				for ( var i = 0; i < modules.length; i++ ) {
793					if ( typeof this[ 'destroy' + modules[ i ] ] !== 'undefined' ) {
794						this[ 'destroy' + modules[ i ] ]();
795					}
796				}
797			}
798
799			// Destroy all slides
800			$.each( this.slides, function( index, element ) {
801				element.destroy();
802			});
803
804			this.slides.length = 0;
805
806			// Move the slides to their initial position in the DOM and 
807			// remove the container elements created dynamically.
808			this.$slides.prependTo( this.$slider );
809			this.$slidesContainer.remove();
810		},
811
812		// Set properties on runtime
813		_setProperties: function( properties, store ) {
814			// Parse the properties passed as an object
815			for ( var prop in properties ) {
816				this.settings[ prop ] = properties[ prop ];
817
818				// Alter the original settings as well unless 'false' is passed to the 'store' parameter
819				if ( store !== false ) {
820					this.originalSettings[ prop ] = properties[ prop ];
821				}
822			}
823
824			this.update();
825		},
826
827		// Attach an event handler to the slider
828		on: function( type, callback ) {
829			return this.$slider.on( type, callback );
830		},
831
832		// Detach an event handler
833		off: function( type ) {
834			return this.$slider.off( type );
835		},
836
837		// Trigger an event on the slider
838		trigger: function( data ) {
839			return this.$slider.triggerHandler( data );
840		},
841
842		// Return the slide at the specified index
843		getSlideAt: function( index ) {
844			return this.slides[ index ];
845		},
846
847		// Return the index of the currently opened slide
848		getSelectedSlide: function() {
849			return this.selectedSlideIndex;
850		},
851
852		// Return the total amount of slides
853		getTotalSlides: function() {
854			return this.slides.length;
855		},
856
857		// The default options of the slider
858		defaults: {
859			// Width of the slide
860			width: 500,
861
862			// Height of the slide
863			height: 300,
864
865			// Indicates if the slider is responsive
866			responsive: true,
867
868			// The aspect ratio of the slider (width/height)
869			aspectRatio: -1,
870
871			// The scale mode for images (cover, contain, exact and none)
872			imageScaleMode: 'cover',
873
874			// Indicates if the image will be centered
875			centerImage: true,
876
877			// Indicates if height of the slider will be adjusted to the
878			// height of the selected slide
879			autoHeight: false,
880
881			// Indicates the initially selected slide
882			startSlide: 0,
883
884			// Indicates if the slides will be shuffled
885			shuffle: false,
886
887			// Indicates whether the slides will be arranged horizontally
888			// or vertically. Can be set to 'horizontal' or 'vertical'.
889			orientation: 'horizontal',
890
891			// Indicates if the size of the slider will be forced to 'fullWidth' or 'fullWindow'
892			forceSize: 'none',
893
894			// Indicates if the slider will be loopable
895			loop: true,
896
897			// The distance between slides
898			slideDistance: 10,
899
900			// The duration of the slide animation
901			slideAnimationDuration: 700,
902
903			// The duration of the height animation
904			heightAnimationDuration: 700,
905
906			// Sets the size of the visible area, allowing the increase of it in order
907			// to make more slides visible.
908			// By default, only the selected slide will be visible. 
909			visibleSize: 'auto',
910
911			// Breakpoints for allowing the slider's options to be changed
912			// based on the size of the window.
913			breakpoints: null,
914
915			// Called when the slider is initialized
916			init: function() {},
917
918			// Called when the slider is updates
919			update: function() {},
920
921			// Called when the slider is resized
922			sliderResize: function() {},
923
924			// Called when a new slide is selected
925			gotoSlide: function() {},
926
927			// Called when the navigation to the newly selected slide is complete
928			gotoSlideComplete: function() {},
929
930			// Called when a breakpoint is reached
931			breakpointReach: function() {}
932		}
933	};
934
935	var SliderProSlide = function( slide, index, settings ) {
936
937		// Reference to the slide jQuery element
938		this.$slide = slide;
939
940		// Reference to the main slide image
941		this.$mainImage = null;
942
943		// Reference to the container that will hold the main image
944		this.$imageContainer = null;
945
946		// Indicates whether the slide has a main image
947		this.hasMainImage = false;
948
949		// Indicates whether the main image is loaded
950		this.isMainImageLoaded = false;
951
952		// Indicates whether the main image is in the process of being loaded
953		this.isMainImageLoading = false;
954
955		// Indicates whether the slide has any image. There could be other images (i.e., in layers)
956		// besides the main slide image.
957		this.hasImages = false;
958
959		// Indicates if all the images in the slide are loaded
960		this.areImagesLoaded = false;
961
962		// The width and height of the slide
963		this.width = 0;
964		this.height = 0;
965
966		// Reference to the global settings of the slider
967		this.settings = settings;
968
969		// Set the index of the slide
970		this.setIndex( index );
971
972		// Initialize the slide
973		this._init();
974	};
975
976	SliderProSlide.prototype = {
977
978		// The starting point for the slide
979		_init: function() {
980			var that = this;
981
982			// Mark the slide as initialized
983			this.$slide.attr( 'data-init', true );
984
985			// Get the main slide image if there is one
986			this.$mainImage = this.$slide.find( '.sp-image' ).length !== 0 ? this.$slide.find( '.sp-image' ) : null;
987
988			// If there is a main slide image, create a container for it and add the image to this container.
989			// The container will allow the isolation of the image from the rest of the slide's content. This is
990			// helpful when you want to show some content below the image and not cover it.
991			if ( this.$mainImage !== null ) {
992				this.hasMainImage = true;
993
994				this.$imageContainer = $( '<div class="sp-image-container"></div>' ).prependTo( this.$slide );
995
996				if ( this.$mainImage.parent( 'a' ).length !== 0 ) {
997					this.$mainImage.parent( 'a' ).appendTo( this.$imageContainer );
998				} else {
999					this.$mainImage.appendTo( this.$imageContainer );
1000				}
1001			}
1002
1003			this.hasImages = this.$slide.find( 'img' ).length !== 0 ? true : false;
1004		},
1005
1006		// Set the size of the slide
1007		setSize: function( width, height ) {
1008			var that = this;
1009			
1010			this.width = width;
1011			this.height = this.settings.autoHeight === true ? 'auto' : height;
1012
1013			this.$slide.css({
1014				'width': this.width,
1015				'height': this.height
1016			});
1017
1018			if ( this.hasMainImage === true ) {
1019				this.$imageContainer.css({
1020					'width': this.width,
1021					'height': this.height
1022				});
1023
1024				// Resize the main image if it's loaded. If the 'data-src' attribute is present it means
1025				// that the image will be lazy-loaded
1026				if ( typeof this.$mainImage.attr( 'data-src' ) === 'undefined' ) {
1027					this.resizeMainImage();
1028				}
1029			}
1030		},
1031
1032		// Get the size (width and height) of the slide
1033		getSize: function() {
1034			var that = this,
1035				size;
1036
1037			// Check if all images have loaded, and if they have, return the size, else, return 'loading'
1038			if ( this.hasImages === true && this.areImagesLoaded === false && typeof this.$slide.attr( 'data-loading' ) === 'undefined' ) {
1039				this.$slide.attr( 'data-loading', true );
1040
1041				var status = SliderProUtils.checkImagesComplete( this.$slide, function() {
1042					that.areImagesLoaded = true;
1043					that.$slide.removeAttr( 'data-loading' );
1044					that.trigger({ type: 'imagesLoaded.' + NS, index: that.index });
1045				});
1046
1047				if ( status === 'complete' ) {
1048					size = this.calculateSize();
1049
1050					return {
1051						'width': size.width,
1052						'height': size.height
1053					};
1054				} else {
1055					return 'loading';
1056				}
1057			} else {
1058				size = this.calculateSize();
1059
1060				return {
1061					'width': size.width,
1062					'height': size.height
1063				};
1064			}
1065		},
1066
1067		// Calculate the width and height of the slide by going
1068		// through all the child elements and measuring their 'bottom'
1069		// and 'right' properties. The element with the biggest
1070		// 'right'/'bottom' property will determine the slide's
1071		// width/height.
1072		calculateSize: function() {
1073			var width = this.$slide.width(),
1074				height = this.$slide.height();
1075
1076			this.$slide.children().each(function( index, element ) {
1077				var child = $( element );
1078
1079				if ( child.is( ':hidden' ) === true ) {
1080					return;
1081				}
1082
1083				var	rect = element.getBoundingClientRect(),
1084					bottom = child.position().top + ( rect.bottom - rect.top ),
1085					right = child.position().left + ( rect.right - rect.left );
1086
1087				if ( bottom > height ) {
1088					height = bottom;
1089				}
1090
1091				if ( right > width ) {
1092					width = right;
1093				}
1094			});
1095
1096			return { width: width, height: height };
1097		},
1098
1099		// Resize the main image.
1100		// 
1101		// Call this when the slide resizes or when the main image has changed to a different image.
1102		resizeMainImage: function( isNewImage ) {
1103			var that = this;
1104
1105			// If the main image has changed, reset the 'flags'
1106			if ( isNewImage === true ) {
1107				this.isMainImageLoaded = false;
1108				this.isMainImageLoading = false;
1109			}
1110
1111			// If the image was not loaded yet and it's not in the process of being loaded, load it
1112			if ( this.isMainImageLoaded === false && this.isMainImageLoading === false ) {
1113				this.isMainImageLoading = true;
1114
1115				SliderProUtils.checkImagesComplete( this.$mainImage, function() {
1116					that.isMainImageLoaded = true;
1117					that.isMainImageLoading = false;
1118					that.resizeMainImage();
1119					that.trigger({ type: 'imagesLoaded.' + NS, index: that.index });
1120				});
1121
1122				return;
1123			}
1124
1125			// After the main image has loaded, resize it
1126			if ( this.settings.autoHeight === true ) {
1127				this.$mainImage.css({ width: '100%', height: 'auto', 'marginLeft': '', 'marginTop': '' });
1128			} else {
1129				if ( this.settings.imageScaleMode === 'cover' ) {
1130					if ( this.$mainImage.width() / this.$mainImage.height() <= this.width / this.height ) {
1131						this.$mainImage.css({ width: '100%', height: 'auto' });
1132					} else {
1133						this.$mainImage.css({ width: 'auto', height: '100%' });
1134					}
1135				} else if ( this.settings.imageScaleMode === 'contain' ) {
1136					if ( this.$mainImage.width() / this.$mainImage.height() >= this.width / this.height ) {
1137						this.$mainImage.css({ width: '100%', height: 'auto' });
vendor: 80,668 bytes, lines 1138-3448
1138					} else {
1139						this.$mainImage.css({ width: 'auto', height: '100%' });
1140					}
1141				} else if ( this.settings.imageScaleMode === 'exact' ) {
1142					this.$mainImage.css({ width: '100%', height: '100%' });
1143				}
1144
1145				if ( this.settings.centerImage === true ) {
1146					this.$mainImage.css({ 'marginLeft': ( this.$imageContainer.width() - this.$mainImage.width() ) * 0.5, 'marginTop': ( this.$imageContainer.height() - this.$mainImage.height() ) * 0.5 });
1147				}
1148			}
1149		},
1150
1151		// Destroy the slide
1152		destroy: function() {
1153			// Clean the slide element from attached styles and data
1154			this.$slide.removeAttr( 'style' );
1155			this.$slide.removeAttr( 'data-init' );
1156			this.$slide.removeAttr( 'data-index' );
1157			this.$slide.removeAttr( 'data-loaded' );
1158
1159			// If there is a main image, remove its container
1160			if ( this.hasMainImage === true ) {
1161				this.$slide.find( '.sp-image' )
1162					.removeAttr( 'style' )
1163					.appendTo( this.$slide );
1164
1165				this.$slide.find( '.sp-image-container' ).remove();
1166			}
1167		},
1168
1169		// Return the index of the slide
1170		getIndex: function() {
1171			return this.index;
1172		},
1173
1174		// Set the index of the slide
1175		setIndex: function( index ) {
1176			this.index = index;
1177			this.$slide.attr( 'data-index', this.index );
1178		},
1179
1180		// Attach an event handler to the slide
1181		on: function( type, callback ) {
1182			return this.$slide.on( type, callback );
1183		},
1184
1185		// Detach an event handler to the slide
1186		off: function( type ) {
1187			return this.$slide.off( type );
1188		},
1189
1190		// Trigger an event on the slide
1191		trigger: function( data ) {
1192			return this.$slide.triggerHandler( data );
1193		}
1194	};
1195
1196	window.SliderPro = SliderPro;
1197	window.SliderProSlide = SliderProSlide;
1198
1199	$.fn.sliderPro = function( options ) {
1200		var args = Array.prototype.slice.call( arguments, 1 );
1201
1202		return this.each(function() {
1203			// Instantiate the slider or alter it
1204			if ( typeof $( this ).data( 'sliderPro' ) === 'undefined' ) {
1205				var newInstance = new SliderPro( this, options );
1206
1207				// Store a reference to the instance created
1208				$( this ).data( 'sliderPro', newInstance );
1209			} else if ( typeof options !== 'undefined' ) {
1210				var	currentInstance = $( this ).data( 'sliderPro' );
1211
1212				// Check the type of argument passed
1213				if ( typeof currentInstance[ options ] === 'function' ) {
1214					currentInstance[ options ].apply( currentInstance, args );
1215				} else if ( typeof currentInstance.settings[ options ] !== 'undefined' ) {
1216					var obj = {};
1217					obj[ options ] = args[ 0 ];
1218					currentInstance._setProperties( obj );
1219				} else if ( typeof options === 'object' ) {
1220					currentInstance._setProperties( options );
1221				} else {
1222					$.error( options + ' does not exist in sliderPro.' );
1223				}
1224			}
1225		});
1226	};
1227
1228	// Contains useful utility functions
1229	var SliderProUtils = {
1230
1231		// Indicates what type of animations are supported in the current browser
1232		// Can be CSS 3D, CSS 2D or JavaScript
1233		supportedAnimation: null,
1234
1235		// Indicates the required vendor prefix for the current browser
1236		vendorPrefix: null,
1237
1238		// Indicates the name of the transition's complete event for the current browser
1239		transitionEvent: null,
1240
1241		// Indicates if the current browser is Internet Explorer (any version)
1242		isIE: null,
1243
1244		// Check whether CSS3 3D or 2D transforms are supported. If they aren't, use JavaScript animations
1245		getSupportedAnimation: function() {
1246			if ( this.supportedAnimation !== null ) {
1247				return this.supportedAnimation;
1248			}
1249
1250			var element = document.body || document.documentElement,
1251				elementStyle = element.style,
1252				isCSSTransitions = typeof elementStyle.transition !== 'undefined' ||
1253									typeof elementStyle.WebkitTransition !== 'undefined' ||
1254									typeof elementStyle.MozTransition !== 'undefined' ||
1255									typeof elementStyle.OTransition !== 'undefined';
1256
1257			if ( isCSSTransitions === true ) {
1258				var div = document.createElement( 'div' );
1259
1260				// Check if 3D transforms are supported
1261				if ( typeof div.style.WebkitPerspective !== 'undefined' || typeof div.style.perspective !== 'undefined' ) {
1262					this.supportedAnimation = 'css-3d';
1263				}
1264
1265				// Additional checks for Webkit
1266				if ( this.supportedAnimation === 'css-3d' && typeof div.styleWebkitPerspective !== 'undefined' ) {
1267					var style = document.createElement( 'style' );
1268					style.textContent = '@media (transform-3d),(-webkit-transform-3d){#test-3d{left:9px;position:absolute;height:5px;margin:0;padding:0;border:0;}}';
1269					document.getElementsByTagName( 'head' )[0].appendChild( style );
1270
1271					div.id = 'test-3d';
1272					document.body.appendChild( div );
1273
1274					if ( ! ( div.offsetLeft === 9 && div.offsetHeight === 5 ) ) {
1275						this.supportedAnimation = null;
1276					}
1277
1278					style.parentNode.removeChild( style );
1279					div.parentNode.removeChild( div );
1280				}
1281
1282				// If CSS 3D transforms are not supported, check if 2D transforms are supported
1283				if ( this.supportedAnimation === null && ( typeof div.style['-webkit-transform'] !== 'undefined' || typeof div.style.transform !== 'undefined' ) ) {
1284					this.supportedAnimation = 'css-2d';
1285				}
1286			} else {
1287				this.supportedAnimation = 'javascript';
1288			}
1289			
1290			return this.supportedAnimation;
1291		},
1292
1293		// Check what vendor prefix should be used in the current browser
1294		getVendorPrefix: function() {
1295			if ( this.vendorPrefix !== null ) {
1296				return this.vendorPrefix;
1297			}
1298
1299			var div = document.createElement( 'div' ),
1300				prefixes = [ 'Webkit', 'Moz', 'ms', 'O' ];
1301			
1302			if ( 'transform' in div.style ) {
1303				this.vendorPrefix = '';
1304				return this.vendorPrefix;
1305			}
1306			
1307			for ( var i = 0; i < prefixes.length; i++ ) {
1308				if ( ( prefixes[ i ] + 'Transform' ) in div.style ) {
1309					this.vendorPrefix = '-' + prefixes[ i ].toLowerCase() + '-';
1310					break;
1311				}
1312			}
1313			
1314			return this.vendorPrefix;
1315		},
1316
1317		// Check the name of the transition's complete event in the current browser
1318		getTransitionEvent: function() {
1319			if ( this.transitionEvent !== null ) {
1320				return this.transitionEvent;
1321			}
1322
1323			var div = document.createElement( 'div' ),
1324				transitions = {
1325					'transition': 'transitionend',
1326					'WebkitTransition': 'webkitTransitionEnd',
1327					'MozTransition': 'transitionend',
1328					'OTransition': 'oTransitionEnd'
1329				};
1330
1331			for ( var transition in transitions ) {
1332				if ( transition in div.style ) {
1333					this.transitionEvent = transitions[ transition ];
1334					break;
1335				}
1336			}
1337
1338			return this.transitionEvent;
1339		},
1340
1341		// If a single image is passed, check if it's loaded.
1342		// If a different element is passed, check if there are images
1343		// inside it, and check if these images are loaded.
1344		checkImagesComplete: function( target, callback ) {
1345			var that = this,
1346
1347				// Check the initial status of the image(s)
1348				status = this.checkImagesStatus( target );
1349
1350			// If there are loading images, wait for them to load.
1351			// If the images are loaded, call the callback function directly.
1352			if ( status === 'loading' ) {
1353				var checkImages = setInterval(function() {
1354					status = that.checkImagesStatus( target );
1355
1356					if ( status === 'complete' ) {
1357						clearInterval( checkImages );
1358
1359						if ( typeof callback === 'function' ) {
1360							callback();
1361						}
1362					}
1363				}, 100 );
1364			} else if ( typeof callback === 'function' ) {
1365				callback();
1366			}
1367
1368			return status;
1369		},
1370
1371		checkImagesStatus: function( target ) {
1372			var status = 'complete';
1373
1374			if ( target.is( 'img' ) && target[0].complete === false ) {
1375				status = 'loading';
1376			} else {
1377				target.find( 'img' ).each(function( index ) {
1378					var image = $( this )[0];
1379
1380					if ( image.complete === false ) {
1381						status = 'loading';
1382					}
1383				});
1384			}
1385
1386			return status;
1387		},
1388
1389		checkIE: function() {
1390			if ( this.isIE !== null ) {
1391				return this.isIE;
1392			}
1393
1394			var userAgent = window.navigator.userAgent,
1395				msie = userAgent.indexOf( 'MSIE' );
1396
1397			if ( userAgent.indexOf( 'MSIE' ) !== -1 || userAgent.match( /Trident.*rv\:11\./ ) ) {
1398				this.isIE = true;
1399			} else {
1400				this.isIE = false;
1401			}
1402
1403			return this.isIE;
1404		}
1405	};
1406
1407	window.SliderProUtils = SliderProUtils;
1408
1409})( window, jQuery );
1410
1411// Thumbnails module for Slider Pro.
1412// 
1413// Adds the possibility to create a thumbnail scroller, each thumbnail
1414// corresponding to a slide.
1415;(function( window, $ ) {
1416
1417	"use strict";
1418
1419	var NS = 'Thumbnails.' + $.SliderPro.namespace;
1420
1421	var Thumbnails = {
1422
1423		// Reference to the thumbnail scroller 
1424		$thumbnails: null,
1425
1426		// Reference to the container of the thumbnail scroller
1427		$thumbnailsContainer: null,
1428
1429		// List of Thumbnail objects
1430		thumbnails: null,
1431
1432		// Index of the selected thumbnail
1433		selectedThumbnailIndex: 0,
1434
1435		// Total size (width or height, depending on the orientation) of the thumbnails
1436		thumbnailsSize: 0,
1437
1438		// Size of the thumbnail's container
1439		thumbnailsContainerSize: 0,
1440
1441		// The position of the thumbnail scroller inside its container
1442		thumbnailsPosition: 0,
1443
1444		// Orientation of the thumbnails
1445		thumbnailsOrientation: null,
1446
1447		// Indicates the 'left' or 'top' position based on the orientation of the thumbnails
1448		thumbnailsPositionProperty: null,
1449
1450		// Indicates if there are thumbnails in the slider
1451		isThumbnailScroller: false,
1452
1453		initThumbnails: function() {
1454			var that = this;
1455
1456			this.thumbnails = [];
1457
1458			this.on( 'update.' + NS, $.proxy( this._thumbnailsOnUpdate, this ) );
1459			this.on( 'sliderResize.' + NS, $.proxy( this._thumbnailsOnResize, this ) );
1460			this.on( 'gotoSlide.' + NS, function( event ) {
1461				that._gotoThumbnail( event.index );
1462			});
1463		},
1464
1465		// Called when the slider is updated
1466		_thumbnailsOnUpdate: function() {
1467			var that = this;
1468
1469			if ( this.$slider.find( '.sp-thumbnail' ).length === 0 && this.thumbnails.length === 0 ) {
1470				this.isThumbnailScroller = false;
1471				return;
1472			}
1473
1474			this.isThumbnailScroller = true;
1475
1476			// Create the container of the thumbnail scroller, if it wasn't created yet
1477			if ( this.$thumbnailsContainer === null ) {
1478				this.$thumbnailsContainer = $( '<div class="sp-thumbnails-container"></div>' ).insertAfter( this.$slidesContainer );
1479			}
1480
1481			// If the thumbnails' main container doesn't exist, create it, and get a reference to it
1482			if ( this.$thumbnails === null ) {
1483				if ( this.$slider.find( '.sp-thumbnails' ).length !== 0 ) {
1484					this.$thumbnails = this.$slider.find( '.sp-thumbnails' ).appendTo( this.$thumbnailsContainer );
1485
1486					// Shuffle/randomize the thumbnails
1487					if ( this.settings.shuffle === true ) {
1488						var thumbnails = this.$thumbnails.find( '.sp-thumbnail' ),
1489							shuffledThumbnails = [];
1490
1491						// Reposition the thumbnails based on the order of the indexes in the
1492						// 'shuffledIndexes' array
1493						$.each( this.shuffledIndexes, function( index, element ) {
1494							var thumbnail = $( thumbnails[ element ] );
1495
1496							if ( thumbnail.parent( 'a' ).length !== 0 ) {
1497								thumbnail = thumbnail.parent( 'a' );
1498							}
1499
1500							shuffledThumbnails.push( thumbnail );
1501						});
1502						
1503						// Append the sorted thumbnails to the thumbnail scroller
1504						this.$thumbnails.empty().append( shuffledThumbnails ) ;
1505					}
1506				} else {
1507					this.$thumbnails = $( '<div class="sp-thumbnails"></div>' ).appendTo( this.$thumbnailsContainer );
1508				}
1509			}
1510
1511			// Check if there are thumbnails inside the slides and move them in the thumbnails container
1512			this.$slides.find( '.sp-thumbnail' ).each( function( index ) {
1513				var $thumbnail = $( this ),
1514					thumbnailIndex = $thumbnail.parents( '.sp-slide' ).index(),
1515					lastThumbnailIndex = that.$thumbnails.find( '.sp-thumbnail' ).length - 1;
1516
1517				// If the index of the slide that contains the thumbnail is greater than the total number
1518				// of thumbnails from the thumbnails container, position the thumbnail at the end.
1519				// Otherwise, add the thumbnails at the corresponding position.
1520				if ( thumbnailIndex > lastThumbnailIndex ) {
1521					$thumbnail.appendTo( that.$thumbnails );
1522				} else {
1523					$thumbnail.insertBefore( that.$thumbnails.find( '.sp-thumbnail' ).eq( thumbnailIndex ) );
1524				}
1525			});
1526
1527			// Loop through the Thumbnail objects and if a corresponding element is not found in the DOM,
1528			// it means that the thumbnail might have been removed. In this case, destroy that Thumbnail instance.
1529			for ( var i = this.thumbnails.length - 1; i >= 0; i-- ) {
1530				if ( this.$thumbnails.find( '.sp-thumbnail[data-index="' + i + '"]' ).length === 0 ) {
1531					var thumbnail = this.thumbnails[ i ];
1532
1533					thumbnail.destroy();
1534					this.thumbnails.splice( i, 1 );
1535				}
1536			}
1537
1538			// Loop through the thumbnails and if there is any uninitialized thumbnail,
1539			// initialize it, else update the thumbnail's index.
1540			this.$thumbnails.find( '.sp-thumbnail' ).each(function( index ) {
1541				var $thumbnail = $( this );
1542
1543				if ( typeof $thumbnail.attr( 'data-init' ) === 'undefined' ) {
1544					that._createThumbnail( $thumbnail, index );
1545				} else {
1546					that.thumbnails[ index ].setIndex( index );
1547				}
1548			});
1549
1550			// Remove the previous class that corresponds to the position of the thumbnail scroller
1551			this.$thumbnailsContainer.removeClass( 'sp-top-thumbnails sp-bottom-thumbnails sp-left-thumbnails sp-right-thumbnails' );
1552
1553			// Check the position of the thumbnail scroller and assign it the appropriate class and styling
1554			if ( this.settings.thumbnailsPosition === 'top' ) {
1555				this.$thumbnailsContainer.addClass( 'sp-top-thumbnails' );
1556				this.thumbnailsOrientation = 'horizontal';
1557			} else if ( this.settings.thumbnailsPosition === 'bottom' ) {
1558				this.$thumbnailsContainer.addClass( 'sp-bottom-thumbnails' );
1559				this.thumbnailsOrientation = 'horizontal';
1560			} else if ( this.settings.thumbnailsPosition === 'left' ) {
1561				this.$thumbnailsContainer.addClass( 'sp-left-thumbnails' );
1562				this.thumbnailsOrientation = 'vertical';
1563			} else if ( this.settings.thumbnailsPosition === 'right' ) {
1564				this.$thumbnailsContainer.addClass( 'sp-right-thumbnails' );
1565				this.thumbnailsOrientation = 'vertical';
1566			}
1567
1568			// Check if the pointer needs to be created
1569			if ( this.settings.thumbnailPointer === true ) {
1570				this.$thumbnailsContainer.addClass( 'sp-has-pointer' );
1571			} else {
1572				this.$thumbnailsContainer.removeClass( 'sp-has-pointer' );
1573			}
1574
1575			// Mark the thumbnail that corresponds to the selected slide
1576			this.selectedThumbnailIndex = this.selectedSlideIndex;
1577			this.$thumbnails.find( '.sp-thumbnail-container' ).eq( this.selectedThumbnailIndex ).addClass( 'sp-selected-thumbnail' );
1578			
1579			// Calculate the total size of the thumbnails
1580			this.thumbnailsSize = 0;
1581
1582			$.each( this.thumbnails, function( index, thumbnail ) {
1583				thumbnail.setSize( that.settings.thumbnailWidth, that.settings.thumbnailHeight );
1584				that.thumbnailsSize += that.thumbnailsOrientation === 'horizontal' ? thumbnail.getSize().width : thumbnail.getSize().height;
1585			});
1586
1587			// Set the size of the thumbnails
1588			if ( this.thumbnailsOrientation === 'horizontal' ) {
1589				this.$thumbnails.css({ 'width': this.thumbnailsSize, 'height': this.settings.thumbnailHeight });
1590				this.$thumbnailsContainer.css( 'height', '' );
1591				this.thumbnailsPositionProperty = 'left';
1592			} else {
1593				this.$thumbnails.css({ 'width': this.settings.thumbnailWidth, 'height': this.thumbnailsSize });
1594				this.$thumbnailsContainer.css( 'width', '' );
1595				this.thumbnailsPositionProperty = 'top';
1596			}
1597
1598			// Fire the 'thumbnailsUpdate' event
1599			this.trigger({ type: 'thumbnailsUpdate' });
1600			if ( $.isFunction( this.settings.thumbnailsUpdate ) ) {
1601				this.settings.thumbnailsUpdate.call( this, { type: 'thumbnailsUpdate' } );
1602			}
1603		},
1604
1605		// Create an individual thumbnail
1606		_createThumbnail: function( element, index ) {
1607			var that = this,
1608				thumbnail = new Thumbnail( element, this.$thumbnails, index );
1609
1610			// When the thumbnail is clicked, navigate to the corresponding slide
1611			thumbnail.on( 'thumbnailClick.' + NS, function( event ) {
1612				that.gotoSlide( event.index );
1613			});
1614
1615			// Add the thumbnail at the specified index
1616			this.thumbnails.splice( index, 0, thumbnail );
1617		},
1618
1619		// Called when the slider is resized.
1620		// Resets the size and position of the thumbnail scroller container.
1621		_thumbnailsOnResize: function() {
1622			if ( this.isThumbnailScroller === false ) {
1623				return;
1624			}
1625
1626			var that = this,
1627				newThumbnailsPosition;
1628
1629			if ( this.thumbnailsOrientation === 'horizontal' ) {
1630				this.thumbnailsContainerSize = Math.min( this.$slidesMask.width(), this.thumbnailsSize );
1631				this.$thumbnailsContainer.css( 'width', this.thumbnailsContainerSize );
1632
1633				// Reduce the slide mask's height, to make room for the thumbnails
1634				if ( this.settings.forceSize === 'fullWindow' ) {
1635					this.$slidesMask.css( 'height', this.$slidesMask.height() - this.$thumbnailsContainer.outerHeight( true ) );
1636
1637					// Resize the slide
1638					this.slideHeight = this.$slidesMask.height();
1639						
1640					$.each( this.slides, function( index, element ) {
1641						element.setSize( that.slideWidth, that.slideHeight );
1642					});
1643				}
1644			} else if ( this.thumbnailsOrientation === 'vertical' ) {
1645
1646				// Check if the width of the slide mask plus the width of the thumbnail scroller is greater than
1647				// the width of the slider's container and if that's the case, reduce the slides container width
1648				// in order to make the entire slider fit inside the slider's container.
1649				if ( this.$slidesMask.width() + this.$thumbnailsContainer.outerWidth( true ) > this.$slider.parent().width() ) {
1650					// Reduce the slider's width, to make room for the thumbnails
1651					if ( this.settings.forceSize === 'fullWidth' || this.settings.forceSize === 'fullWindow' ) {
1652						this.$slider.css( 'max-width', $( window ).width() - this.$thumbnailsContainer.outerWidth( true ) );
1653					} else {
1654						this.$slider.css( 'max-width', this.$slider.parent().width() - this.$thumbnailsContainer.outerWidth( true ) );
1655					}
1656					
1657					this.$slidesMask.css( 'width', this.$slider.width() );
1658
1659					// If the slides are horizontally oriented, update the visible size and the offset
1660					// of the selected slide, since the slider's size was reduced to make room for the thumbnails.
1661					// 
1662					// If the slides are vertically oriented, update the width and height (to maintain the aspect ratio)
1663					// of the slides.
1664					if ( this.settings.orientation === 'horizontal' ) {
1665						this.visibleOffset = Math.round( ( this.$slider.width() - this.slideSize ) / 2 );
1666						this.visibleSlidesSize = this.$slidesMask.width();
1667					} else if ( this.settings.orientation === 'vertical' ) {
1668						this.slideWidth = this.$slider.width();
1669
1670						$.each( this.slides, function( index, element ) {
1671							element.setSize( that.slideWidth, that.slideHeight );
1672						});
1673					}
1674
1675					// Re-arrange the slides
1676					this._resetSlidesPosition();
1677				}
1678
1679				this.thumbnailsContainerSize = Math.min( this.$slidesMask.height(), this.thumbnailsSize );
1680				this.$thumbnailsContainer.css( 'height', this.thumbnailsContainerSize );
1681			}
1682
1683			// If the total size of the thumbnails is smaller than the thumbnail scroller' container (which has
1684			// the same size as the slides container), it means that all the thumbnails will be visible, so set
1685			// the position of the thumbnail scroller to 0.
1686			// 
1687			// If that's not the case, the thumbnail scroller will be positioned based on which thumbnail is selected.
1688			if ( this.thumbnailsSize <= this.thumbnailsContainerSize || this.$thumbnails.find( '.sp-selected-thumbnail' ).length === 0 ) {
1689				newThumbnailsPosition = 0;
1690			} else {
1691				newThumbnailsPosition = Math.max( - this.thumbnails[ this.selectedThumbnailIndex ].getPosition()[ this.thumbnailsPositionProperty ], this.thumbnailsContainerSize - this.thumbnailsSize );
1692			}
1693
1694			// Add a padding to the slider, based on the thumbnail scroller's orientation, to make room
1695			// for the thumbnails.
1696			if ( this.settings.thumbnailsPosition === 'top' ) {
1697				this.$slider.css({ 'paddingTop': this.$thumbnailsContainer.outerHeight( true ), 'paddingLeft': '', 'paddingRight': '' });
1698			} else if ( this.settings.thumbnailsPosition === 'bottom' ) {
1699				this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': '' });
1700			} else if ( this.settings.thumbnailsPosition === 'left' ) {
1701				this.$slider.css({ 'paddingTop': '', 'paddingLeft': this.$thumbnailsContainer.outerWidth( true ), 'paddingRight': '' });
1702			} else if ( this.settings.thumbnailsPosition === 'right' ) {
1703				this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': this.$thumbnailsContainer.outerWidth( true ) });
1704			}
1705
1706			this._moveThumbnailsTo( newThumbnailsPosition, true );
1707		},
1708
1709		// Selects the thumbnail at the indicated index and moves the thumbnail scroller
1710		// accordingly.
1711		_gotoThumbnail: function( index ) {
1712			if ( this.isThumbnailScroller === false || typeof this.thumbnails[ index ] === 'undefined' ) {
1713				return;
1714			}
1715
1716			var previousIndex = this.selectedThumbnailIndex,
1717				newThumbnailsPosition = this.thumbnailsPosition;
1718
1719			this.selectedThumbnailIndex = index;
1720
1721			// Set the 'selected' class to the appropriate thumbnail
1722			this.$thumbnails.find( '.sp-selected-thumbnail' ).removeClass( 'sp-selected-thumbnail' );
1723			this.$thumbnails.find( '.sp-thumbnail-container' ).eq( this.selectedThumbnailIndex ).addClass( 'sp-selected-thumbnail' );
1724
1725			// Calculate the new position that the thumbnail scroller needs to go to.
1726			// 
1727			// If the selected thumbnail has a higher index than the previous one, make sure that the thumbnail
1728			// that comes after the selected thumbnail will be visible, if the selected thumbnail is not the
1729			// last thumbnail in the list.
1730			// 
1731			// If the selected thumbnail has a lower index than the previous one, make sure that the thumbnail
1732			// that's before the selected thumbnail will be visible, if the selected thumbnail is not the
1733			// first thumbnail in the list.
1734			if ( this.selectedThumbnailIndex >= previousIndex ) {
1735				var nextThumbnailIndex = this.selectedThumbnailIndex === this.thumbnails.length - 1 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex + 1,
1736					nextThumbnail = this.thumbnails[ nextThumbnailIndex ],
1737					nextThumbnailPosition = this.thumbnailsOrientation === 'horizontal' ? nextThumbnail.getPosition().right : nextThumbnail.getPosition().bottom,
1738					thumbnailsRightPosition = - this.thumbnailsPosition + this.thumbnailsContainerSize;
1739
1740				if ( nextThumbnailPosition > thumbnailsRightPosition ) {
1741					newThumbnailsPosition = this.thumbnailsPosition - ( nextThumbnailPosition - thumbnailsRightPosition );
1742				}
1743			} else if ( this.selectedThumbnailIndex < previousIndex ) {
1744				var previousThumbnailIndex = this.selectedThumbnailIndex === 0 ? this.selectedThumbnailIndex : this.selectedThumbnailIndex - 1,
1745					previousThumbnail = this.thumbnails[ previousThumbnailIndex ],
1746					previousThumbnailPosition = this.thumbnailsOrientation === 'horizontal' ? previousThumbnail.getPosition().left : previousThumbnail.getPosition().top;
1747
1748				if ( previousThumbnailPosition < - this.thumbnailsPosition ) {
1749					newThumbnailsPosition = - previousThumbnailPosition;
1750				}
1751			}
1752
1753			// Move the thumbnail scroller to the calculated position
1754			this._moveThumbnailsTo( newThumbnailsPosition );
1755
1756			// Fire the 'gotoThumbnail' event
1757			this.trigger({ type: 'gotoThumbnail' });
1758			if ( $.isFunction( this.settings.gotoThumbnail ) ) {
1759				this.settings.gotoThumbnail.call( this, { type: 'gotoThumbnail' });
1760			}
1761		},
1762
1763		// Move the thumbnail scroller to the indicated position
1764		_moveThumbnailsTo: function( position, instant, callback ) {
1765			var that = this,
1766				css = {};
1767
1768			// Return if the position hasn't changed
1769			if ( position === this.thumbnailsPosition ) {
1770				return;
1771			}
1772
1773			this.thumbnailsPosition = position;
1774
1775			// Use CSS transitions if they are supported. If not, use JavaScript animation
1776			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
1777				var transition,
1778					left = this.thumbnailsOrientation === 'horizontal' ? position : 0,
1779					top = this.thumbnailsOrientation === 'horizontal' ? 0 : position;
1780
1781				if ( this.supportedAnimation === 'css-3d' ) {
1782					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
1783				} else {
1784					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
1785				}
1786
1787				if ( typeof instant !== 'undefined' && instant === true ) {
1788					transition = '';
1789				} else {
1790					this.$thumbnails.addClass( 'sp-animated' );
1791					transition = this.vendorPrefix + 'transform ' + 700 / 1000 + 's';
1792
1793					this.$thumbnails.on( this.transitionEvent, function( event ) {
1794						if ( event.target !== event.currentTarget ) {
1795							return;
1796						}
1797
1798						that.$thumbnails.off( that.transitionEvent );
1799						that.$thumbnails.removeClass( 'sp-animated' );
1800
1801						if ( typeof callback === 'function' ) {
1802							callback();
1803						}
1804
1805						// Fire the 'thumbnailsMoveComplete' event
1806						that.trigger({ type: 'thumbnailsMoveComplete' });
1807						if ( $.isFunction( that.settings.thumbnailsMoveComplete ) ) {
1808							that.settings.thumbnailsMoveComplete.call( that, { type: 'thumbnailsMoveComplete' });
1809						}
1810					});
1811				}
1812
1813				css[ this.vendorPrefix + 'transition' ] = transition;
1814
1815				this.$thumbnails.css( css );
1816			} else {
1817				css[ 'margin-' + this.thumbnailsPositionProperty ] = position;
1818
1819				if ( typeof instant !== 'undefined' && instant === true ) {
1820					this.$thumbnails.css( css );
1821				} else {
1822					this.$thumbnails
1823						.addClass( 'sp-animated' )
1824						.animate( css, 700, function() {
1825							that.$thumbnails.removeClass( 'sp-animated' );
1826
1827							if ( typeof callback === 'function' ) {
1828								callback();
1829							}
1830
1831							// Fire the 'thumbnailsMoveComplete' event
1832							that.trigger({ type: 'thumbnailsMoveComplete' });
1833							if ( $.isFunction( that.settings.thumbnailsMoveComplete ) ) {
1834								that.settings.thumbnailsMoveComplete.call( that, { type: 'thumbnailsMoveComplete' });
1835							}
1836						});
1837				}
1838			}
1839		},
1840
1841		// Stop the movement of the thumbnail scroller
1842		_stopThumbnailsMovement: function() {
1843			var css = {};
1844
1845			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
1846				var	matrixString = this.$thumbnails.css( this.vendorPrefix + 'transform' ),
1847					matrixType = matrixString.indexOf( 'matrix3d' ) !== -1 ? 'matrix3d' : 'matrix',
1848					matrixArray = matrixString.replace( matrixType, '' ).match( /-?[0-9\.]+/g ),
1849					left = matrixType === 'matrix3d' ? parseInt( matrixArray[ 12 ], 10 ) : parseInt( matrixArray[ 4 ], 10 ),
1850					top = matrixType === 'matrix3d' ? parseInt( matrixArray[ 13 ], 10 ) : parseInt( matrixArray[ 5 ], 10 );
1851
1852				if ( this.supportedAnimation === 'css-3d' ) {
1853					css[ this.vendorPrefix + 'transform' ] = 'translate3d(' + left + 'px, ' + top + 'px, 0)';
1854				} else {
1855					css[ this.vendorPrefix + 'transform' ] = 'translate(' + left + 'px, ' + top + 'px)';
1856				}
1857
1858				css[ this.vendorPrefix + 'transition' ] = '';
1859
1860				this.$thumbnails.css( css );
1861				this.$thumbnails.off( this.transitionEvent );
1862				this.thumbnailsPosition = this.thumbnailsOrientation === 'horizontal' ? parseInt( matrixArray[ 4 ] , 10 ) : parseInt( matrixArray[ 5 ] , 10 );
1863			} else {
1864				this.$thumbnails.stop();
1865				this.thumbnailsPosition = parseInt( this.$thumbnails.css( 'margin-' + this.thumbnailsPositionProperty ), 10 );
1866			}
1867
1868			this.$thumbnails.removeClass( 'sp-animated' );
1869		},
1870
1871		// Destroy the module
1872		destroyThumbnails: function() {
1873			var that = this;
1874
1875			// Remove event listeners
1876			this.off( 'update.' + NS );
1877
1878			if ( this.isThumbnailScroller === false ) {
1879				return;
1880			}
1881			
1882			this.off( 'sliderResize.' + NS );
1883			this.off( 'gotoSlide.' + NS );
1884			$( window ).off( 'resize.' + this.uniqueId + '.' + NS );
1885
1886			// Destroy the individual thumbnails
1887			this.$thumbnails.find( '.sp-thumbnail' ).each( function() {
1888				var $thumbnail = $( this ),
1889					index = parseInt( $thumbnail.attr( 'data-index' ), 10 ),
1890					thumbnail = that.thumbnails[ index ];
1891
1892				thumbnail.off( 'thumbnailClick.' + NS );
1893				thumbnail.destroy();
1894			});
1895
1896			this.thumbnails.length = 0;
1897
1898			// Add the thumbnail scroller directly in the slider and
1899			// remove the thumbnail scroller container
1900			this.$thumbnails.appendTo( this.$slider );
1901			this.$thumbnailsContainer.remove();
1902			
1903			// Remove any created padding
1904			this.$slider.css({ 'paddingTop': '', 'paddingLeft': '', 'paddingRight': '' });
1905		},
1906
1907		thumbnailsDefaults: {
1908
1909			// Sets the width of the thumbnail
1910			thumbnailWidth: 100,
1911
1912			// Sets the height of the thumbnail
1913			thumbnailHeight: 80,
1914
1915			// Sets the position of the thumbnail scroller (top, bottom, right, left)
1916			thumbnailsPosition: 'bottom',
1917
1918			// Indicates if a pointer will be displayed for the selected thumbnail
1919			thumbnailPointer: false,
1920
1921			// Called when the thumbnails are updated
1922			thumbnailsUpdate: function() {},
1923
1924			// Called when a new thumbnail is selected
1925			gotoThumbnail: function() {},
1926
1927			// Called when the thumbnail scroller has moved
1928			thumbnailsMoveComplete: function() {}
1929		}
1930	};
1931
1932	var Thumbnail = function( thumbnail, thumbnails, index ) {
1933
1934		// Reference to the thumbnail jQuery element
1935		this.$thumbnail = thumbnail;
1936
1937		// Reference to the thumbnail scroller
1938		this.$thumbnails = thumbnails;
1939
1940		// Reference to the thumbnail's container, which will be 
1941		// created dynamically.
1942		this.$thumbnailContainer = null;
1943
1944		// The width and height of the thumbnail
1945		this.width = 0;
1946		this.height = 0;
1947
1948		// Indicates whether the thumbnail's image is loaded
1949		this.isImageLoaded = false;
1950
1951		// Set the index of the slide
1952		this.setIndex( index );
1953
1954		// Initialize the thumbnail
1955		this._init();
1956	};
1957
1958	Thumbnail.prototype = {
1959
1960		_init: function() {
1961			var that = this;
1962
1963			// Mark the thumbnail as initialized
1964			this.$thumbnail.attr( 'data-init', true );
1965
1966			// Create a container for the thumbnail and add the original thumbnail to this container.
1967			// Having a container will help crop the thumbnail image if it's too large.
1968			this.$thumbnailContainer = $( '<div class="sp-thumbnail-container"></div>' ).appendTo( this.$thumbnails );
1969
1970			if ( this.$thumbnail.parent( 'a' ).length !== 0 ) {
1971				this.$thumbnail.parent( 'a' ).appendTo( this.$thumbnailContainer );
1972			} else {
1973				this.$thumbnail.appendTo( this.$thumbnailContainer );
1974			}
1975
1976			// When the thumbnail container is clicked, fire an event
1977			this.$thumbnailContainer.on( 'click.' + NS, function() {
1978				that.trigger({ type: 'thumbnailClick.' + NS, index: that.index });
1979			});
1980		},
1981
1982		// Set the width and height of the thumbnail
1983		setSize: function( width, height ) {
1984			this.width = width;
1985			this.height = height;
1986
1987			// Apply the width and height to the thumbnail's container
1988			this.$thumbnailContainer.css({ 'width': this.width, 'height': this.height });
1989
1990			// If there is an image, resize it to fit the thumbnail container
1991			if ( this.$thumbnail.is( 'img' ) && typeof this.$thumbnail.attr( 'data-src' ) === 'undefined' ) {
1992				this.resizeImage();
1993			}
1994		},
1995
1996		// Return the width and height of the thumbnail
1997		getSize: function() {
1998			return {
1999				width: this.$thumbnailContainer.outerWidth( true ),
2000				height: this.$thumbnailContainer.outerHeight( true )
2001			};
2002		},
2003
2004		// Return the top, bottom, left and right position of the thumbnail
2005		getPosition: function() {
2006			return {
2007				left: this.$thumbnailContainer.position().left + parseInt( this.$thumbnailContainer.css( 'marginLeft' ) , 10 ),
2008				right: this.$thumbnailContainer.position().left + parseInt( this.$thumbnailContainer.css( 'marginLeft' ) , 10 ) + this.$thumbnailContainer.outerWidth(),
2009				top: this.$thumbnailContainer.position().top + parseInt( this.$thumbnailContainer.css( 'marginTop' ) , 10 ),
2010				bottom: this.$thumbnailContainer.position().top + parseInt( this.$thumbnailContainer.css( 'marginTop' ) , 10 ) + this.$thumbnailContainer.outerHeight()
2011			};
2012		},
2013
2014		// Set the index of the thumbnail
2015		setIndex: function( index ) {
2016			this.index = index;
2017			this.$thumbnail.attr( 'data-index', this.index );
2018		},
2019
2020		// Resize the thumbnail's image
2021		resizeImage: function() {
2022			var that = this;
2023
2024			// If the image is not loaded yet, load it
2025			if ( this.isImageLoaded === false ) {
2026				SliderProUtils.checkImagesComplete( this.$thumbnailContainer , function() {
2027					that.isImageLoaded = true;
2028					that.resizeImage();
2029				});
2030
2031				return;
2032			}
2033
2034			// Get the reference to the thumbnail image again because it was replaced by
2035			// another img element during the loading process
2036			this.$thumbnail = this.$thumbnailContainer.find( '.sp-thumbnail' );
2037
2038			// Calculate whether the image should stretch horizontally or vertically
2039			var imageWidth = this.$thumbnail.width(),
2040				imageHeight = this.$thumbnail.height();
2041
2042			if ( imageWidth / imageHeight <= this.width / this.height ) {
2043				this.$thumbnail.css({ width: '100%', height: 'auto' });
2044			} else {
2045				this.$thumbnail.css({ width: 'auto', height: '100%' });
2046			}
2047
2048			this.$thumbnail.css({ 'marginLeft': ( this.$thumbnailContainer.width() - this.$thumbnail.width() ) * 0.5, 'marginTop': ( this.$thumbnailContainer.height() - this.$thumbnail.height() ) * 0.5 });
2049		},
2050
2051		// Destroy the thumbnail
2052		destroy: function() {
2053			this.$thumbnailContainer.off( 'click.' + NS );
2054
2055			// Remove added attributes
2056			this.$thumbnail.removeAttr( 'data-init' );
2057			this.$thumbnail.removeAttr( 'data-index' );
2058
2059			// Remove the thumbnail's container and add the thumbnail
2060			// back to the thumbnail scroller container
2061			if ( this.$thumbnail.parent( 'a' ).length !== 0 ) {
2062				this.$thumbnail.parent( 'a' ).insertBefore( this.$thumbnailContainer );
2063			} else {
2064				this.$thumbnail.insertBefore( this.$thumbnailContainer );
2065			}
2066			
2067			this.$thumbnailContainer.remove();
2068		},
2069
2070		// Attach an event handler to the slide
2071		on: function( type, callback ) {
2072			return this.$thumbnailContainer.on( type, callback );
2073		},
2074
2075		// Detach an event handler to the slide
2076		off: function( type ) {
2077			return this.$thumbnailContainer.off( type );
2078		},
2079
2080		// Trigger an event on the slide
2081		trigger: function( data ) {
2082			return this.$thumbnailContainer.triggerHandler( data );
2083		}
2084	};
2085
2086	$.SliderPro.addModule( 'Thumbnails', Thumbnails );
2087
2088})( window, jQuery );
2089
2090// ConditionalImages module for Slider Pro.
2091// 
2092// Adds the possibility to specify multiple sources for each image and
2093// load the image that's the most appropriate for the size of the slider.
2094// For example, instead of loading a large image even if the slider will be small
2095// you can specify a smaller image that will be loaded instead.
2096;(function( window, $ ) {
2097
2098	"use strict";
2099
2100	var NS = 'ConditionalImages.' + $.SliderPro.namespace;
2101
2102	var ConditionalImages = {
2103
2104		// Reference to the previous size
2105		previousImageSize: null,
2106
2107		// Reference to the current size
2108		currentImageSize: null,
2109
2110		// Indicates if the current display supports high PPI
2111		isRetinaScreen: false,
2112
2113		initConditionalImages: function() {
2114			this.currentImageSize = this.previousImageSize = 'default';
2115			this.isRetinaScreen = ( typeof this._isRetina !== 'undefined' ) && ( this._isRetina() === true );
2116
2117			this.on( 'update.' + NS, $.proxy( this._conditionalImagesOnUpdate, this ) );
2118			this.on( 'sliderResize.' + NS, $.proxy( this._conditionalImagesOnResize, this ) );
2119		},
2120
2121		// Loop through all the existing images and specify the original path of the image
2122		// inside the 'data-default' attribute.
2123		_conditionalImagesOnUpdate: function() {
2124			$.each( this.slides, function( index, element ) {
2125				var $slide = element.$slide;
2126
2127				$slide.find( 'img:not([ data-default ])' ).each(function() {
2128					var $image = $( this );
2129
2130					if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) {
2131						$image.attr( 'data-default', $image.attr( 'data-src' ) );
2132					} else {
2133						$image.attr( 'data-default', $image.attr( 'src' ) );
2134					}
2135				});
2136			});
2137		},
2138
2139		// When the window resizes, identify the applyable image size based on the current size of the slider
2140		// and apply it to all images that have a version of the image specified for this size.
2141		_conditionalImagesOnResize: function() {
2142			if ( this.slideWidth <= this.settings.smallSize ) {
2143				this.currentImageSize = 'small';
2144			} else if ( this.slideWidth <= this.settings.mediumSize ) {
2145				this.currentImageSize = 'medium';
2146			} else if ( this.slideWidth <= this.settings.largeSize ) {
2147				this.currentImageSize = 'large';
2148			} else {
2149				this.currentImageSize = 'default';
2150			}
2151
2152			if ( this.previousImageSize !== this.currentImageSize ) {
2153				var that = this;
2154
2155				$.each( this.slides, function( index, element ) {
2156					var $slide = element.$slide;
2157
2158					$slide.find( 'img' ).each(function() {
2159						var $image = $( this ),
2160							imageSource = '';
2161
2162						// Check if the current display supports high PPI and if a retina version of the current size was specified
2163						if ( that.isRetinaScreen === true && typeof $image.attr( 'data-retina' + that.currentImageSize ) !== 'undefined' ) {
2164							imageSource = $image.attr( 'data-retina' + that.currentImageSize );
2165
2166							// If the retina image was not loaded yet, replace the default image source with the one
2167							// that corresponds to the current slider size
2168							if ( typeof $image.attr( 'data-retina' ) !== 'undefined' && $image.attr( 'data-retina' ) !== imageSource ) {
2169								$image.attr( 'data-retina', imageSource );
2170							}
2171						} else if ( ( that.isRetinaScreen === false || that.isRetinaScreen === true && typeof $image.attr( 'data-retina' ) === 'undefined' ) && typeof $image.attr( 'data-' + that.currentImageSize ) !== 'undefined' ) {
2172							imageSource = $image.attr( 'data-' + that.currentImageSize );
2173
2174							// If the image is set to lazy load, replace the image source with the one
2175							// that corresponds to the current slider size
2176							if ( typeof $image.attr( 'data-src' ) !== 'undefined' && $image.attr( 'data-src' ) !== imageSource ) {
2177								$image.attr( 'data-src', imageSource );
2178							}
2179						}
2180
2181						// If a new image was found
2182						if ( imageSource !== '' ) {
2183
2184							// The existence of the 'data-src' attribute indicates that the image
2185							// will be lazy loaded, so don't load the new image yet
2186							if ( typeof $image.attr( 'data-src' ) === 'undefined' && $image.attr( 'src' ) !== imageSource  ) {
2187								that._loadConditionalImage( $image, imageSource, function( newImage ) {
2188									if ( newImage.hasClass( 'sp-image' ) ) {
2189										element.$mainImage = newImage;
2190										element.resizeMainImage( true );
2191									}
2192								});
2193							}
2194						}
2195					});
2196				});
2197
2198				this.previousImageSize = this.currentImageSize;
2199			}
2200		},
2201
2202		// Replace the target image with a new image
2203		_loadConditionalImage: function( image, source, callback ) {
2204
2205			// Create a new image element
2206			var newImage = $( new Image() );
2207
2208			// Copy the class(es) and inline style
2209			newImage.attr( 'class', image.attr( 'class' ) );
2210			newImage.attr( 'style', image.attr( 'style' ) );
2211
2212			// Copy the data attributes
2213			$.each( image.data(), function( name, value ) {
2214				newImage.attr( 'data-' + name, value );
2215			});
2216
2217			// Copy the width and height attributes if they exist
2218			if ( typeof image.attr( 'width' ) !== 'undefined') {
2219				newImage.attr( 'width', image.attr( 'width' ) );
2220			}
2221
2222			if ( typeof image.attr( 'height' ) !== 'undefined') {
2223				newImage.attr( 'height', image.attr( 'height' ) );
2224			}
2225
2226			if ( typeof image.attr( 'alt' ) !== 'undefined' ) {
2227				newImage.attr( 'alt', image.attr( 'alt' ) );
2228			}
2229
2230			if ( typeof image.attr( 'title' ) !== 'undefined' ) {
2231				newImage.attr( 'title', image.attr( 'title' ) );
2232			}
2233
2234			newImage.attr( 'src', source );
2235
2236			// Add the new image in the same container and remove the older image
2237			newImage.insertAfter( image );
2238			image.remove();
2239			image = null;
2240				
2241			if ( typeof callback === 'function' ) {
2242				callback( newImage );
2243			}
2244		},
2245
2246		// Destroy the module
2247		destroyConditionalImages: function() {
2248			this.off( 'update.' + NS );
2249			this.off( 'sliderResize.' + NS );
2250		},
2251
2252		conditionalImagesDefaults: {
2253
2254			// If the slider size is below this size, the small version of the images will be used
2255			smallSize: 480,
2256
2257			// If the slider size is below this size, the small version of the images will be used
2258			mediumSize: 768,
2259
2260			// If the slider size is below this size, the small version of the images will be used
2261			largeSize: 1024
2262		}
2263	};
2264
2265	$.SliderPro.addModule( 'ConditionalImages', ConditionalImages );
2266
2267})( window, jQuery );
2268
2269// Retina module for Slider Pro.
2270// 
2271// Adds the possibility to load a different image when the slider is
2272// viewed on a retina screen.
2273;(function( window, $ ) {
2274
2275	"use strict";
2276	
2277	var NS = 'Retina.' + $.SliderPro.namespace;
2278
2279	var Retina = {
2280
2281		initRetina: function() {
2282			var that = this;
2283
2284			// Return if it's not a retina screen
2285			if ( this._isRetina() === false ) {
2286				return;
2287			}
2288			
2289			this.on( 'update.' + NS, $.proxy( this._checkRetinaImages, this ) );
2290
2291			if ( this.$slider.find( '.sp-thumbnail' ).length !== 0 ) {
2292				this.on( 'update.Thumbnails.' + NS, $.proxy( this._checkRetinaThumbnailImages, this ) );
2293			}
2294		},
2295
2296		// Checks if the current display supports high PPI
2297		_isRetina: function() {
2298			if ( window.devicePixelRatio >= 2 ) {
2299				return true;
2300			}
2301
2302			if ( window.matchMedia && ( window.matchMedia( "(-webkit-min-device-pixel-ratio: 2),(min-resolution: 2dppx)" ).matches ) ) {
2303				return true;
2304			}
2305
2306			return false;
2307		},
2308
2309		// Loop through the slides and replace the images with their retina version
2310		_checkRetinaImages: function() {
2311			var that = this;
2312
2313			$.each( this.slides, function( index, element ) {
2314				var $slide = element.$slide;
2315
2316				if ( typeof $slide.attr( 'data-retina-loaded' ) === 'undefined' ) {
2317					$slide.attr( 'data-retina-loaded', true );
2318
2319					$slide.find( 'img[data-retina]' ).each(function() {
2320						var $image = $( this );
2321
2322						if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) {
2323							$image.attr( 'data-src', $image.attr( 'data-retina' ) );
2324						} else {
2325							that._loadRetinaImage( $image, function( newImage ) {
2326								if ( newImage.hasClass( 'sp-image' ) ) {
2327									element.$mainImage = newImage;
2328									element.resizeMainImage( true );
2329								}
2330							});
2331						}
2332					});
2333				}
2334			});
2335		},
2336
2337		// Loop through the thumbnails and replace the images with their retina version
2338		_checkRetinaThumbnailImages: function() {
2339			var that = this;
2340
2341			$.each( this.thumbnails, function( index, element ) {
2342				var $thumbnail = element.$thumbnailContainer;
2343
2344				if ( typeof $thumbnail.attr( 'data-retina-loaded' ) === 'undefined' ) {
2345					$thumbnail.attr( 'data-retina-loaded', true );
2346
2347					$thumbnail.find( 'img[data-retina]' ).each(function() {
2348						var $image = $( this );
2349
2350						if ( typeof $image.attr( 'data-src' ) !== 'undefined' ) {
2351							$image.attr( 'data-src', $image.attr( 'data-retina' ) );
2352						} else {
2353							that._loadRetinaImage( $image, function( newImage ) {
2354								if ( newImage.hasClass( 'sp-thumbnail' ) ) {
2355									element.resizeImage();
2356								}
2357							});
2358						}
2359					});
2360				}
2361			});
2362		},
2363
2364		// Load the retina image
2365		_loadRetinaImage: function( image, callback ) {
2366			var retinaFound = false,
2367				newImagePath = '';
2368
2369			// Check if there is a retina image specified
2370			if ( typeof image.attr( 'data-retina' ) !== 'undefined' ) {
2371				retinaFound = true;
2372
2373				newImagePath = image.attr( 'data-retina' );
2374			}
2375
2376			// Check if there is a lazy loaded, non-retina, image specified
2377			if ( typeof image.attr( 'data-src' ) !== 'undefined' ) {
2378				if ( retinaFound === false ) {
2379					newImagePath = image.attr( 'data-src') ;
2380				}
2381
2382				image.removeAttr('data-src');
2383			}
2384
2385			// Return if there isn't a retina or lazy loaded image
2386			if ( newImagePath === '' ) {
2387				return;
2388			}
2389
2390			// Create a new image element
2391			var newImage = $( new Image() );
2392
2393			// Copy the class(es) and inline style
2394			newImage.attr( 'class', image.attr('class') );
2395			newImage.attr( 'style', image.attr('style') );
2396
2397			// Copy the data attributes
2398			$.each( image.data(), function( name, value ) {
2399				newImage.attr( 'data-' + name, value );
2400			});
2401
2402			// Copy the width and height attributes if they exist
2403			if ( typeof image.attr( 'width' ) !== 'undefined' ) {
2404				newImage.attr( 'width', image.attr( 'width' ) );
2405			}
2406
2407			if ( typeof image.attr( 'height' ) !== 'undefined' ) {
2408				newImage.attr( 'height', image.attr( 'height' ) );
2409			}
2410
2411			if ( typeof image.attr( 'alt' ) !== 'undefined' ) {
2412				newImage.attr( 'alt', image.attr( 'alt' ) );
2413			}
2414
2415			if ( typeof image.attr( 'title' ) !== 'undefined' ) {
2416				newImage.attr( 'title', image.attr( 'title' ) );
2417			}
2418
2419			// Add the new image in the same container and remove the older image
2420			newImage.insertAfter( image );
2421			image.remove();
2422			image = null;
2423
2424			// Assign the source of the image
2425			newImage.attr( 'src', newImagePath );
2426
2427			if ( typeof callback === 'function' ) {
2428				callback( newImage );
2429			}
2430		},
2431
2432		// Destroy the module
2433		destroyRetina: function() {
2434			this.off( 'update.' + NS );
2435			this.off( 'update.Thumbnails.' + NS );
2436		}
2437	};
2438
2439	$.SliderPro.addModule( 'Retina', Retina );
2440	
2441})( window, jQuery );
2442
2443// Lazy Loading module for Slider Pro.
2444// 
2445// Adds the possibility to delay the loading of the images until the slides/thumbnails
2446// that contain them become visible. This technique improves the initial loading
2447// performance.
2448;(function( window, $ ) {
2449
2450	"use strict";
2451
2452	var NS = 'LazyLoading.' + $.SliderPro.namespace;
2453
2454	var LazyLoading = {
2455
2456		allowLazyLoadingCheck: true,
2457
2458		initLazyLoading: function() {
2459			var that = this;
2460
2461			// The 'resize' event is fired after every update, so it's possible to use it for checking
2462			// if the update made new slides become visible
2463			// 
2464			// Also, resizing the slider might make new slides or thumbnails visible
2465			this.on( 'sliderResize.' + NS, $.proxy( this._lazyLoadingOnResize, this ) );
2466
2467			// Check visible images when a new slide is selected
2468			this.on( 'gotoSlide.' + NS, $.proxy( this._checkAndLoadVisibleImages, this ) );
2469
2470			// Check visible thumbnail images when the thumbnails are updated because new thumbnail
2471			// might have been added or the settings might have been changed so that more thumbnail
2472			// images become visible
2473			// 
2474			// Also, check visible thumbnail images after the thumbnails have moved because new thumbnails might
2475			// have become visible
2476			this.on( 'thumbnailsUpdate.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, $.proxy( this._checkAndLoadVisibleThumbnailImages, this ) );
2477		},
2478
2479		_lazyLoadingOnResize: function() {
2480			var that = this;
2481
2482			if ( this.allowLazyLoadingCheck === false ) {
2483				return;
2484			}
2485
2486			this.allowLazyLoadingCheck = false;
2487			
2488			this._checkAndLoadVisibleImages();
2489
2490			if ( this.$slider.find( '.sp-thumbnail' ).length !== 0 ) {
2491				this._checkAndLoadVisibleThumbnailImages();
2492			}
2493
2494			// Use a timer to deffer the loading of images in order to prevent too many
2495			// checking attempts
2496			setTimeout(function() {
2497				that.allowLazyLoadingCheck = true;
2498			}, 500 );
2499		},
2500
2501		// Check visible slides and load their images
2502		_checkAndLoadVisibleImages: function() {
2503			if ( this.$slider.find( '.sp-slide:not([ data-loaded ])' ).length === 0 ) {
2504				return;
2505			}
2506
2507			var that = this,
2508
2509				// Use either the middle position or the index of the selected slide as a reference, depending on
2510				// whether the slider is loopable
2511				referencePosition = this.settings.loop === true ? this.middleSlidePosition : this.selectedSlideIndex,
2512
2513				// Calculate how many slides are visible at the sides of the selected slide
2514				visibleOnSides = Math.ceil( ( this.visibleSlidesSize - this.slideSize ) / 2 / this.slideSize ),
2515
2516				// Calculate the indexes of the first and last slide that will be checked
2517				from = referencePosition - visibleOnSides - 1 > 0 ? referencePosition - visibleOnSides - 1 : 0,
2518				to = referencePosition + visibleOnSides + 1 < this.getTotalSlides() - 1 ? referencePosition + visibleOnSides + 1 : this.getTotalSlides() - 1,
2519				
2520				// Get all the slides that need to be checked
2521				slidesToCheck = this.slidesOrder.slice( from, to + 1 );
2522
2523			// Loop through the selected slides and if the slide is not marked as having
2524			// been loaded yet, loop through its images and load them.
2525			$.each( slidesToCheck, function( index, element ) {
2526				var slide = that.slides[ element ],
2527					$slide = slide.$slide;
2528
2529				if ( typeof $slide.attr( 'data-loaded' ) === 'undefined' ) {
2530					$slide.attr( 'data-loaded', true );
2531
2532					$slide.find( 'img[ data-src ]' ).each(function() {
2533						var image = $( this );
2534						that._loadImage( image, function( newImage ) {
2535							if ( newImage.hasClass( 'sp-image' ) ) {
2536								slide.$mainImage = newImage;
2537								slide.resizeMainImage( true );
2538							}
2539						});
2540					});
2541				}
2542			});
2543		},
2544
2545		// Check visible thumbnails and load their images
2546		_checkAndLoadVisibleThumbnailImages: function() {
2547			if ( this.$slider.find( '.sp-thumbnail-container:not([ data-loaded ])' ).length === 0 ) {
2548				return;
2549			}
2550
2551			var that = this,
2552				thumbnailSize = this.thumbnailsSize / this.thumbnails.length,
2553
2554				// Calculate the indexes of the first and last thumbnail that will be checked
2555				from = Math.floor( Math.abs( this.thumbnailsPosition / thumbnailSize ) ),
2556				to = Math.floor( ( - this.thumbnailsPosition + this.thumbnailsContainerSize ) / thumbnailSize ),
2557
2558				// Get all the thumbnails that need to be checked
2559				thumbnailsToCheck = this.thumbnails.slice( from, to + 1 );
2560
2561			// Loop through the selected thumbnails and if the thumbnail is not marked as having
2562			// been loaded yet, load its image.
2563			$.each( thumbnailsToCheck, function( index, element ) {
2564				var $thumbnailContainer = element.$thumbnailContainer;
2565
2566				if ( typeof $thumbnailContainer.attr( 'data-loaded' ) === 'undefined' ) {
2567					$thumbnailContainer.attr( 'data-loaded', true );
2568
2569					$thumbnailContainer.find( 'img[ data-src ]' ).each(function() {
2570						var image = $( this );
2571
2572						that._loadImage( image, function() {
2573							element.resizeImage();
2574						});
2575					});
2576				}
2577			});
2578		},
2579
2580		// Load an image
2581		_loadImage: function( image, callback ) {
2582			// Create a new image element
2583			var newImage = $( new Image() );
2584
2585			// Copy the class(es) and inline style
2586			newImage.attr( 'class', image.attr( 'class' ) );
2587			newImage.attr( 'style', image.attr( 'style' ) );
2588
2589			// Copy the data attributes
2590			$.each( image.data(), function( name, value ) {
2591				newImage.attr( 'data-' + name, value );
2592			});
2593
2594			// Copy the width and height attributes if they exist
2595			if ( typeof image.attr( 'width' ) !== 'undefined') {
2596				newImage.attr( 'width', image.attr( 'width' ) );
2597			}
2598
2599			if ( typeof image.attr( 'height' ) !== 'undefined') {
2600				newImage.attr( 'height', image.attr( 'height' ) );
2601			}
2602
2603			if ( typeof image.attr( 'alt' ) !== 'undefined' ) {
2604				newImage.attr( 'alt', image.attr( 'alt' ) );
2605			}
2606
2607			if ( typeof image.attr( 'title' ) !== 'undefined' ) {
2608				newImage.attr( 'title', image.attr( 'title' ) );
2609			}
2610
2611			// Assign the source of the image
2612			newImage.attr( 'src', image.attr( 'data-src' ) );
2613			newImage.removeAttr( 'data-src' );
2614
2615			// Add the new image in the same container and remove the older image
2616			newImage.insertAfter( image );
2617			image.remove();
2618			image = null;
2619			
2620			if ( typeof callback === 'function' ) {
2621				callback( newImage );
2622			}
2623		},
2624
2625		// Destroy the module
2626		destroyLazyLoading: function() {
2627			this.off( 'update.' + NS );
2628			this.off( 'gotoSlide.' + NS );
2629			this.off( 'sliderResize.' + NS );
2630			this.off( 'thumbnailsUpdate.' + NS );
2631			this.off( 'thumbnailsMoveComplete.' + NS );
2632		}
2633	};
2634
2635	$.SliderPro.addModule( 'LazyLoading', LazyLoading );
2636
2637})( window, jQuery );
2638
2639// Layers module for Slider Pro.
2640// 
2641// Adds support for animated and static layers. The layers can contain any content,
2642// from simple text for video elements.
2643;(function( window, $ ) {
2644
2645	"use strict";
2646
2647	var NS = 'Layers.' +  $.SliderPro.namespace;
2648
2649	var Layers = {
2650
2651		// Reference to the original 'gotoSlide' method
2652		layersGotoSlideReference: null,
2653
2654		// Reference to the timer that will delay the overriding
2655		// of the 'gotoSlide' method
2656		waitForLayersTimer: null,
2657
2658		initLayers: function() {
2659			this.on( 'update.' + NS, $.proxy( this._layersOnUpdate, this ) );
2660			this.on( 'sliderResize.' + NS, $.proxy( this._layersOnResize, this ) );
2661			this.on( 'gotoSlide.' + NS, $.proxy( this._layersOnGotoSlide, this ) );
2662		},
2663
2664		// Loop through the slides and initialize all layers
2665		_layersOnUpdate: function( event ) {
2666			var that = this;
2667
2668			$.each( this.slides, function( index, element ) {
2669				var $slide = element.$slide;
2670
2671				// Initialize the layers
2672				this.$slide.find( '.sp-layer:not([ data-init ])' ).each(function() {
2673					var layer = new Layer( $( this ) );
2674
2675					// Add the 'layers' array to the slide objects (instance of SliderProSlide)
2676					if ( typeof element.layers === 'undefined' ) {
2677						element.layers = [];
2678					}
2679
2680					element.layers.push( layer );
2681
2682					if ( $( this ).hasClass( 'sp-static' ) === false ) {
2683
2684						// Add the 'animatedLayers' array to the slide objects (instance of SliderProSlide)
2685						if ( typeof element.animatedLayers === 'undefined' ) {
2686							element.animatedLayers = [];
2687						}
2688
2689						element.animatedLayers.push( layer );
2690					}
2691				});
2692			});
2693
2694			// If the 'waitForLayers' option is enabled, the slider will not move to another slide
2695			// until all the layers from the previous slide will be hidden. To achieve this,
2696			// replace the current 'gotoSlide' function with another function that will include the 
2697			// required functionality.
2698			// 
2699			// Since the 'gotoSlide' method might be overridden by other modules as well, delay this
2700			// override to make sure it's the last override.
2701			if ( this.settings.waitForLayers === true ) {
2702				clearTimeout( this.waitForLayersTimer );
2703
2704				this.waitForLayersTimer = setTimeout(function() {
2705					that.layersGotoSlideReference = that.gotoSlide;
2706					that.gotoSlide = that._layersGotoSlide;
2707				}, 1 );
2708			}
2709		},
2710
2711		// When the slider resizes, try to scale down the layers proportionally. The automatic scaling
2712		// will make use of an option, 'autoScaleReference', by comparing the current width of the slider
2713		// with the reference width. So, if the reference width is 1000 pixels and the current width is
2714		// 500 pixels, it means that the layers will be scaled down to 50% of their size.
2715		_layersOnResize: function() {
2716			var that = this,
2717				autoScaleReference,
2718				useAutoScale = this.settings.autoScaleLayers,
2719				scaleRatio;
2720
2721			if ( this.settings.autoScaleLayers === false ) {
2722				// Show the layers for the initial slide
2723				this.showLayers( this.selectedSlideIndex );
2724				
2725				return;
2726			}
2727
2728			// If there isn't a reference for how the layers should scale down automatically, use the 'width'
2729			// option as a reference, unless the width was set to a percentage. If there isn't a set reference and
2730			// the width was set to a percentage, auto scaling will not be used because it's not possible to
2731			// calculate how much should the layers scale.
2732			if ( this.settings.autoScaleReference === -1 ) {
2733				if ( typeof this.settings.width === 'string' && this.settings.width.indexOf( '%' ) !== -1 ) {
2734					useAutoScale = false;
2735				} else {
2736					autoScaleReference = parseInt( this.settings.width, 10 );
2737				}
2738			} else {
2739				autoScaleReference = this.settings.autoScaleReference;
2740			}
2741
2742			if ( useAutoScale === true && this.slideWidth < autoScaleReference ) {
2743				scaleRatio = that.slideWidth / autoScaleReference;
2744			} else {
2745				scaleRatio = 1;
2746			}
2747
2748			$.each( this.slides, function( index, slide ) {
2749				if ( typeof slide.layers !== 'undefined' ) {
2750					$.each( slide.layers, function( index, layer ) {
2751						layer.scale( scaleRatio );
2752					});
2753				}
2754			});
2755
2756			// Show the layers for the initial slide
2757			this.showLayers( this.selectedSlideIndex );
2758		},
2759
2760		// Replace the 'gotoSlide' method with this one, which makes it possible to 
2761		// change the slide only after the layers from the previous slide are hidden.
2762		_layersGotoSlide: function( index ) {
2763			var that = this,
2764				animatedLayers = this.slides[ this.selectedSlideIndex ].animatedLayers;
2765
2766			// If the slider is dragged, don't wait for the layer to hide
2767			if ( this.$slider.hasClass( 'sp-swiping' ) || typeof animatedLayers === 'undefined' || animatedLayers.length === 0  ) {
2768				this.layersGotoSlideReference( index );
2769			} else {
2770				this.on( 'hideLayersComplete.' + NS, function() {
2771					that.off( 'hideLayersComplete.' + NS );
2772					that.layersGotoSlideReference( index );
2773				});
2774
2775				this.hideLayers( this.selectedSlideIndex );
2776			}
2777		},
2778
2779		// When a new slide is selected, hide the layers from the previous slide
2780		// and show the layers from the current slide.
2781		_layersOnGotoSlide: function( event ) {
2782			if ( this.previousSlideIndex !== this.selectedSlideIndex ) {
2783				this.hideLayers( this.previousSlideIndex );
2784			}
2785
2786			this.showLayers( this.selectedSlideIndex );
2787		},
2788
2789		// Show the animated layers from the slide at the specified index,
2790		// and fire an event when all the layers from the slide become visible.
2791		showLayers: function( index ) {
2792			var that = this,
2793				animatedLayers = this.slides[ index ].animatedLayers,
2794				layerCounter = 0;
2795
2796			if ( typeof animatedLayers === 'undefined' ) {
2797				return;
2798			}
2799
2800			$.each( animatedLayers, function( index, element ) {
2801
2802				// If the layer is already visible, increment the counter directly, else wait 
2803				// for the layer's showing animation to complete.
2804				if ( element.isVisible() === true ) {
2805					layerCounter++;
2806
2807					if ( layerCounter === animatedLayers.length ) {
2808						that.trigger({ type: 'showLayersComplete', index: index });
2809						if ( $.isFunction( that.settings.showLayersComplete ) ) {
2810							that.settings.showLayersComplete.call( that, { type: 'showLayersComplete', index: index });
2811						}
2812					}
2813				} else {
2814					element.show(function() {
2815						layerCounter++;
2816
2817						if ( layerCounter === animatedLayers.length ) {
2818							that.trigger({ type: 'showLayersComplete', index: index });
2819							if ( $.isFunction( that.settings.showLayersComplete ) ) {
2820								that.settings.showLayersComplete.call( that, { type: 'showLayersComplete', index: index });
2821							}
2822						}
2823					});
2824				}
2825			});
2826		},
2827
2828		// Hide the animated layers from the slide at the specified index,
2829		// and fire an event when all the layers from the slide become invisible.
2830		hideLayers: function( index ) {
2831			var that = this,
2832				animatedLayers = this.slides[ index ].animatedLayers,
2833				layerCounter = 0;
2834
2835			if ( typeof animatedLayers === 'undefined' ) {
2836				return;
2837			}
2838
2839			$.each( animatedLayers, function( index, element ) {
2840
2841				// If the layer is already invisible, increment the counter directly, else wait 
2842				// for the layer's hiding animation to complete.
2843				if ( element.isVisible() === false ) {
2844					layerCounter++;
2845
2846					if ( layerCounter === animatedLayers.length ) {
2847						that.trigger({ type: 'hideLayersComplete', index: index });
2848						if ( $.isFunction( that.settings.hideLayersComplete ) ) {
2849							that.settings.hideLayersComplete.call( that, { type: 'hideLayersComplete', index: index });
2850						}
2851					}
2852				} else {
2853					element.hide(function() {
2854						layerCounter++;
2855
2856						if ( layerCounter === animatedLayers.length ) {
2857							that.trigger({ type: 'hideLayersComplete', index: index });
2858							if ( $.isFunction( that.settings.hideLayersComplete ) ) {
2859								that.settings.hideLayersComplete.call( that, { type: 'hideLayersComplete', index: index });
2860							}
2861						}
2862					});
2863				}
2864			});
2865		},
2866
2867		// Destroy the module
2868		destroyLayers: function() {
2869			this.off( 'update.' + NS );
2870			this.off( 'resize.' + NS );
2871			this.off( 'gotoSlide.' + NS );
2872			this.off( 'hideLayersComplete.' + NS );
2873		},
2874
2875		layersDefaults: {
2876
2877			// Indicates whether the slider will wait for the layers to disappear before
2878			// going to a new slide
2879			waitForLayers: false,
2880
2881			// Indicates whether the layers will be scaled automatically
2882			autoScaleLayers: true,
2883
2884			// Sets a reference width which will be compared to the current slider width
2885			// in order to determine how much the layers need to scale down. By default,
2886			// the reference width will be equal to the slide width. However, if the slide width
2887			// is set to a percentage value, then it's necessary to set a specific value for 'autoScaleReference'.
2888			autoScaleReference: -1,
2889
2890			// Called when all animated layers become visible
2891			showLayersComplete: function() {},
2892
2893			// Called when all animated layers become invisible
2894			hideLayersComplete: function() {}
2895		}
2896	};
2897
2898	// Override the slide's 'destroy' method in order to destroy the 
2899	// layers that where added to the slide as well.
2900	var slideDestroy = window.SliderProSlide.prototype.destroy;
2901
2902	window.SliderProSlide.prototype.destroy = function() {
2903		if ( typeof this.layers !== 'undefined' ) {
2904			$.each( this.layers, function( index, element ) {
2905				element.destroy();
2906			});
2907
2908			this.layers.length = 0;
2909		}
2910
2911		if ( typeof this.animatedLayers !== 'undefined' ) {
2912			this.animatedLayers.length = 0;
2913		}
2914
2915		slideDestroy.apply( this );
2916	};
2917
2918	var Layer = function( layer ) {
2919
2920		// Reference to the layer jQuery element
2921		this.$layer = layer;
2922
2923		// Indicates whether a layer is currently visible or hidden
2924		this.visible = false;
2925
2926		// Indicates whether the layer was styled
2927		this.styled = false;
2928
2929		// Holds the data attributes added to the layer
2930		this.data = null;
2931
2932		// Indicates the layer's reference point (topLeft, bottomLeft, topRight or bottomRight)
2933		this.position = null;
2934		
2935		// Indicates which CSS property (left or right) will be used for positioning the layer 
2936		this.horizontalProperty = null;
2937		
2938		// Indicates which CSS property (top or bottom) will be used for positioning the layer 
2939		this.verticalProperty = null;
2940
2941		// Indicates the value of the horizontal position
2942		this.horizontalPosition = null;
2943		
2944		// Indicates the value of the vertical position
2945		this.verticalPosition = null;
2946
2947		// Indicates how much the layers needs to be scaled
2948		this.scaleRatio = 1;
2949
2950		// Indicates the type of supported transition (CSS3 2D, CSS3 3D or JavaScript)
2951		this.supportedAnimation = SliderProUtils.getSupportedAnimation();
2952
2953		// Indicates the required vendor prefix for CSS (i.e., -webkit, -moz, etc.)
2954		this.vendorPrefix = SliderProUtils.getVendorPrefix();
2955
2956		// Indicates the name of the CSS transition's complete event (i.e., transitionend, webkitTransitionEnd, etc.)
2957		this.transitionEvent = SliderProUtils.getTransitionEvent();
2958
2959		// Reference to the timer that will be used to hide the layers automatically after a given time interval
2960		this.stayTimer = null;
2961
2962		this._init();
2963	};
2964
2965	Layer.prototype = {
2966
2967		// Initialize the layers
2968		_init: function() {
2969			this.$layer.attr( 'data-init', true );
2970
2971			if ( this.$layer.hasClass( 'sp-static' ) ) {
2972				this._setStyle();
2973			} else {
2974				this.$layer.css({ 'visibility': 'hidden', 'display': 'none' });
2975			}
2976		},
2977
2978		// Set the size and position of the layer
2979		_setStyle: function() {
2980			this.styled = true;
2981
2982			this.$layer.css( 'display', '' );
2983
2984			// Get the data attributes specified in HTML
2985			this.data = this.$layer.data();
2986			
2987			if ( typeof this.data.width !== 'undefined' ) {
2988				this.$layer.css( 'width', this.data.width );
2989			}
2990
2991			if ( typeof this.data.height !== 'undefined' ) {
2992				this.$layer.css( 'height', this.data.height );
2993			}
2994
2995			if ( typeof this.data.depth !== 'undefined' ) {
2996				this.$layer.css( 'z-index', this.data.depth );
2997			}
2998
2999			this.position = this.data.position ? ( this.data.position ).toLowerCase() : 'topleft';
3000
3001			if ( this.position.indexOf( 'right' ) !== -1 ) {
3002				this.horizontalProperty = 'right';
3003			} else if ( this.position.indexOf( 'left' ) !== -1 ) {
3004				this.horizontalProperty = 'left';
3005			} else {
3006				this.horizontalProperty = 'center';
3007			}
3008
3009			if ( this.position.indexOf( 'bottom' ) !== -1 ) {
3010				this.verticalProperty = 'bottom';
3011			} else if ( this.position.indexOf( 'top' ) !== -1 ) {
3012				this.verticalProperty = 'top';
3013			} else {
3014				this.verticalProperty = 'center';
3015			}
3016
3017			this._setPosition();
3018
3019			this.scale( this.scaleRatio );
3020		},
3021
3022		// Set the position of the layer
3023		_setPosition: function() {
3024			var inlineStyle = this.$layer.attr( 'style' );
3025
3026			this.horizontalPosition = typeof this.data.horizontal !== 'undefined' ? this.data.horizontal : 0;
3027			this.verticalPosition = typeof this.data.vertical !== 'undefined' ? this.data.vertical : 0;
3028
3029			// Set the horizontal position of the layer based on the data set
3030			if ( this.horizontalProperty === 'center' ) {
3031				
3032				// prevent content wrapping while setting the width
3033				if ( typeof inlineStyle === 'undefined' || ( typeof inlineStyle !== 'undefined' && inlineStyle.indexOf( 'width' ) === -1 ) ) {
3034					this.$layer.css( 'white-space', 'nowrap' );
3035					this.$layer.css( 'width', this.$layer.outerWidth( true ) );
3036				}
3037
3038				this.$layer.css({ 'marginLeft': 'auto', 'marginRight': 'auto', 'left': this.horizontalPosition, 'right': 0 });
3039			} else {
3040				this.$layer.css( this.horizontalProperty, this.horizontalPosition );
3041			}
3042
3043			// Set the vertical position of the layer based on the data set
3044			if ( this.verticalProperty === 'center' ) {
3045
3046				// prevent content wrapping while setting the height
3047				if ( typeof inlineStyle === 'undefined' || ( typeof inlineStyle !== 'undefined' && inlineStyle.indexOf( 'height' ) === -1 ) ) {
3048					this.$layer.css( 'white-space', 'nowrap' );
3049					this.$layer.css( 'height', this.$layer.outerHeight( true ) );
3050				}
3051
3052				this.$layer.css({ 'marginTop': 'auto', 'marginBottom': 'auto', 'top': this.verticalPosition, 'bottom': 0 });
3053			} else {
3054				this.$layer.css( this.verticalProperty, this.verticalPosition );
3055			}
3056		},
3057
3058		// Scale the layer
3059		scale: function( ratio ) {
3060
3061			// Return if the layer is set to be unscalable
3062			if ( this.$layer.hasClass( 'sp-no-scale' ) ) {
3063				return;
3064			}
3065
3066			// Store the ratio (even if the layer is not ready to be scaled yet)
3067			this.scaleRatio = ratio;
3068
3069			// Return if the layer is not styled yet
3070			if ( this.styled === false ) {
3071				return;
3072			}
3073
3074			var horizontalProperty = this.horizontalProperty === 'center' ? 'left' : this.horizontalProperty,
3075				verticalProperty = this.verticalProperty === 'center' ? 'top' : this.verticalProperty,
3076				css = {};
3077
3078			// Apply the scaling
3079			css[ this.vendorPrefix + 'transform-origin' ] = this.horizontalProperty + ' ' + this.verticalProperty;
3080			css[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3081
3082			// If the position is not set to a percentage value, apply the scaling to the position
3083			if ( typeof this.horizontalPosition !== 'string' ) {
3084				css[ horizontalProperty ] = this.horizontalPosition * this.scaleRatio;
3085			}
3086
3087			// If the position is not set to a percentage value, apply the scaling to the position
3088			if ( typeof this.verticalPosition !== 'string' ) {
3089				css[ verticalProperty ] = this.verticalPosition * this.scaleRatio;
3090			}
3091
3092			// If the width or height is set to a percentage value, increase the percentage in order to
3093			// maintain the same layer to slide proportions. This is necessary because otherwise the scaling
3094			// transform would minimize the layers more than intended.
3095			if ( typeof this.data.width === 'string' && this.data.width.indexOf( '%' ) !== -1 ) {
3096				css.width = ( parseInt( this.data.width, 10 ) / this.scaleRatio ).toString() + '%';
3097			}
3098
3099			if ( typeof this.data.height === 'string' && this.data.height.indexOf( '%' ) !== -1 ) {
3100				css.height = ( parseInt( this.data.height, 10 ) / this.scaleRatio ).toString() + '%';
3101			}
3102
3103			this.$layer.css( css );
3104		},
3105
3106		// Show the layer
3107		show: function( callback ) {
3108			if ( this.visible === true ) {
3109				return;
3110			}
3111
3112			this.visible = true;
3113
3114			// First, style the layer if it's not already styled
3115			if ( this.styled === false ) {
3116				this._setStyle();
3117			}
3118
3119			var that = this,
3120				offset = typeof this.data.showOffset !== 'undefined' ? this.data.showOffset : 50,
3121				duration = typeof this.data.showDuration !== 'undefined' ? this.data.showDuration / 1000 : 0.4,
3122				delay = typeof this.data.showDelay !== 'undefined' ? this.data.showDelay : 10,
3123				stayDuration = typeof that.data.stayDuration !== 'undefined' ? parseInt( that.data.stayDuration, 10 ) : -1;
3124
3125			// Animate the layers with CSS3 or with JavaScript
3126			if ( this.supportedAnimation === 'javascript' ) {
3127				this.$layer
3128					.stop()
3129					.delay( delay )
3130					.css({ 'opacity': 0, 'visibility': 'visible' })
3131					.animate( { 'opacity': 1 }, duration * 1000, function() {
3132
3133						// Hide the layer after a given time interval
3134						if ( stayDuration !== -1 ) {
3135							that.stayTimer = setTimeout(function() {
3136								that.hide();
3137								that.stayTimer = null;
3138							}, stayDuration );
3139						}
3140
3141						if ( typeof callback !== 'undefined' ) {
3142							callback();
3143						}
3144					});
3145			} else {
3146				var start = { 'opacity': 0, 'visibility': 'visible' },
3147					target = { 'opacity': 1 },
3148					transformValues = '';
3149
3150				start[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3151				target[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3152				target[ this.vendorPrefix + 'transition' ] = 'opacity ' + duration + 's';
3153
3154				if ( typeof this.data.showTransition !== 'undefined' ) {
3155					if ( this.data.showTransition === 'left' ) {
3156						transformValues = offset + 'px, 0';
3157					} else if ( this.data.showTransition === 'right' ) {
3158						transformValues = '-' + offset + 'px, 0';
3159					} else if ( this.data.showTransition === 'up' ) {
3160						transformValues = '0, ' + offset + 'px';
3161					} else if ( this.data.showTransition === 'down') {
3162						transformValues = '0, -' + offset + 'px';
3163					}
3164
3165					start[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(' + transformValues + ', 0)' : ' translate(' + transformValues + ')';
3166					target[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(0, 0, 0)' : ' translate(0, 0)';
3167					target[ this.vendorPrefix + 'transition' ] += ', ' + this.vendorPrefix + 'transform ' + duration + 's';
3168				}
3169
3170				// Listen when the layer animation is complete
3171				this.$layer.on( this.transitionEvent, function( event ) {
3172					if ( event.target !== event.currentTarget ) {
3173						return;
3174					}
3175
3176					that.$layer
3177						.off( that.transitionEvent )
3178						.css( that.vendorPrefix + 'transition', '' );
3179
3180					// Hide the layer after a given time interval
3181					if ( stayDuration !== -1 ) {
3182						that.stayTimer = setTimeout(function() {
3183							that.hide();
3184							that.stayTimer = null;
3185						}, stayDuration );
3186					}
3187
3188					if ( typeof callback !== 'undefined' ) {
3189						callback();
3190					}
3191				});
3192
3193				this.$layer.css( start );
3194
3195				setTimeout( function() {
3196					that.$layer.css( target );
3197				}, delay );
3198			}
3199		},
3200
3201		// Hide the layer
3202		hide: function( callback ) {
3203			if ( this.visible === false ) {
3204				return;
3205			}
3206
3207			var that = this,
3208				offset = typeof this.data.hideOffset !== 'undefined' ? this.data.hideOffset : 50,
3209				duration = typeof this.data.hideDuration !== 'undefined' ? this.data.hideDuration / 1000 : 0.4,
3210				delay = typeof this.data.hideDelay !== 'undefined' ? this.data.hideDelay : 10;
3211
3212			this.visible = false;
3213
3214			// If the layer is hidden before it hides automatically, clear the timer
3215			if ( this.stayTimer !== null ) {
3216				clearTimeout( this.stayTimer );
3217			}
3218
3219			// Animate the layers with CSS3 or with JavaScript
3220			if ( this.supportedAnimation === 'javascript' ) {
3221				this.$layer
3222					.stop()
3223					.delay( delay )
3224					.animate({ 'opacity': 0 }, duration * 1000, function() {
3225						$( this ).css( 'visibility', 'hidden' );
3226
3227						if ( typeof callback !== 'undefined' ) {
3228							callback();
3229						}
3230					});
3231			} else {
3232				var transformValues = '',
3233					target = { 'opacity': 0 };
3234
3235				target[ this.vendorPrefix + 'transform' ] = 'scale(' + this.scaleRatio + ')';
3236				target[ this.vendorPrefix + 'transition' ] = 'opacity ' + duration + 's';
3237
3238				if ( typeof this.data.hideTransition !== 'undefined' ) {
3239					if ( this.data.hideTransition === 'left' ) {
3240						transformValues = '-' + offset + 'px, 0';
3241					} else if ( this.data.hideTransition === 'right' ) {
3242						transformValues = offset + 'px, 0';
3243					} else if ( this.data.hideTransition === 'up' ) {
3244						transformValues = '0, -' + offset + 'px';
3245					} else if ( this.data.hideTransition === 'down' ) {
3246						transformValues = '0, ' + offset + 'px';
3247					}
3248
3249					target[ this.vendorPrefix + 'transform' ] += this.supportedAnimation === 'css-3d' ? ' translate3d(' + transformValues + ', 0)' : ' translate(' + transformValues + ')';
3250					target[ this.vendorPrefix + 'transition' ] += ', ' + this.vendorPrefix + 'transform ' + duration + 's';
3251				}
3252
3253				// Listen when the layer animation is complete
3254				this.$layer.on( this.transitionEvent, function( event ) {
3255					if ( event.target !== event.currentTarget ) {
3256						return;
3257					}
3258
3259					that.$layer
3260						.off( that.transitionEvent )
3261						.css( that.vendorPrefix + 'transition', '' );
3262
3263					// Hide the layer after transition
3264					if ( that.visible === false ) {
3265						that.$layer.css( 'visibility', 'hidden' );
3266					}
3267
3268					if ( typeof callback !== 'undefined' ) {
3269						callback();
3270					}
3271				});
3272
3273				setTimeout( function() {
3274					that.$layer.css( target );
3275				}, delay );
3276			}
3277		},
3278
3279		isVisible: function() {
3280			if ( this.visible === false || this.$layer.is( ':hidden' ) ) {
3281				return false;
3282			}
3283
3284			return true;
3285		},
3286
3287		// Destroy the layer
3288		destroy: function() {
3289			this.$layer.removeAttr( 'style' );
3290			this.$layer.removeAttr( 'data-init' );
3291		}
3292	};
3293
3294	$.SliderPro.addModule( 'Layers', Layers );
3295	
3296})( window, jQuery );
3297
3298// Fade module for Slider Pro.
3299// 
3300// Adds the possibility to navigate through slides using a cross-fade effect.
3301;(function( window, $ ) {
3302
3303	"use strict";
3304
3305	var NS = 'Fade.' + $.SliderPro.namespace;
3306
3307	var Fade = {
3308
3309		// Reference to the original 'gotoSlide' method
3310		fadeGotoSlideReference: null,
3311
3312		initFade: function() {
3313			this.on( 'update.' + NS, $.proxy( this._fadeOnUpdate, this ) );
3314		},
3315
3316		// If fade is enabled, store a reference to the original 'gotoSlide' method
3317		// and then assign a new function to 'gotoSlide'.
3318		_fadeOnUpdate: function() {
3319			if ( this.settings.fade === true ) {
3320				this.fadeGotoSlideReference = this.gotoSlide;
3321				this.gotoSlide = this._fadeGotoSlide;
3322			}
3323		},
3324
3325		// Will replace the original 'gotoSlide' function by adding a cross-fade effect
3326		// between the previous and the next slide.
3327		_fadeGotoSlide: function( index ) {
3328			if ( index === this.selectedSlideIndex ) {
3329				return;
3330			}
3331			
3332			// If the slides are being swiped/dragged, don't use fade, but call the original method instead.
3333			// If not, which means that a new slide was selected through a button, arrows or direct call, then
3334			// use fade.
3335			if ( this.$slider.hasClass( 'sp-swiping' ) ) {
3336				this.fadeGotoSlideReference( index );
3337			} else {
3338				var that = this,
3339					$nextSlide,
3340					$previousSlide,
3341					newIndex = index;
3342
3343				// Loop through all the slides and overlap the the previous and next slide,
3344				// and hide the other slides.
3345				$.each( this.slides, function( index, element ) {
3346					var slideIndex = element.getIndex(),
3347						$slide = element.$slide;
3348
3349					if ( slideIndex === newIndex ) {
3350						$slide.css({ 'opacity': 0, 'left': 0, 'top': 0, 'z-index': 20 });
3351						$nextSlide = $slide;
3352					} else if ( slideIndex === that.selectedSlideIndex ) {
3353						$slide.css({ 'opacity': 1, 'left': 0, 'top': 0, 'z-index': 10 });
3354						$previousSlide = $slide;
3355					} else {
3356						$slide.css( 'visibility', 'hidden' );
3357					}
3358				});
3359
3360				// Set the new indexes for the previous and selected slides
3361				this.previousSlideIndex = this.selectedSlideIndex;
3362				this.selectedSlideIndex = index;
3363
3364				// Rearrange the slides if the slider is loopable
3365				if ( that.settings.loop === true ) {
3366					that._updateSlidesOrder();
3367				}
3368
3369				// Move the slides container so that the cross-fading slides (which now have the top and left
3370				// position set to 0) become visible and in the center of the slider.
3371				this._moveTo( this.visibleOffset, true );
3372
3373				// Fade out the previous slide, if indicated, in addition to fading in the next slide
3374				if ( this.settings.fadeOutPreviousSlide === true ) {
3375					this._fadeSlideTo( $previousSlide, 0 );
3376				}
3377
3378				// Fade in the selected slide
3379				this._fadeSlideTo( $nextSlide, 1, function() {
3380
3381					// After the animation is over, make all the slides visible again
3382					$.each( that.slides, function( index, element ) {
3383						var $slide = element.$slide;
3384						$slide.css({ 'visibility': '', 'opacity': '', 'z-index': '' });
3385					});
3386					
3387					// Reset the position of the slides and slides container
3388					that._resetSlidesPosition();
3389
3390					// Fire the 'gotoSlideComplete' event
3391					that.trigger({ type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex });
3392					if ( $.isFunction( that.settings.gotoSlideComplete ) ) {
3393						that.settings.gotoSlideComplete.call( that, { type: 'gotoSlideComplete', index: index, previousIndex: that.previousSlideIndex } );
3394					}
3395				});
3396
3397				if ( this.settings.autoHeight === true ) {
3398					this._resizeHeight();
3399				}
3400
3401				// Fire the 'gotoSlide' event
3402				this.trigger({ type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex });
3403				if ( $.isFunction( this.settings.gotoSlide ) ) {
3404					this.settings.gotoSlide.call( this, { type: 'gotoSlide', index: index, previousIndex: this.previousSlideIndex });
3405				}
3406			}
3407		},
3408
3409		// Fade the target slide to the specified opacity (0 or 1)
3410		_fadeSlideTo: function( target, opacity, callback ) {
3411			var that = this;
3412
3413			// Use CSS transitions if they are supported. If not, use JavaScript animation.
3414			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
3415
3416				// There needs to be a delay between the moment the opacity is set
3417				// and the moment the transitions starts.
3418				setTimeout(function(){
3419					var css = { 'opacity': opacity };
3420					css[ that.vendorPrefix + 'transition' ] = 'opacity ' + that.settings.fadeDuration / 1000 + 's';
3421					target.css( css );
3422				}, 1 );
3423
3424				target.on( this.transitionEvent, function( event ) {
3425					if ( event.target !== event.currentTarget ) {
3426						return;
3427					}
3428					
3429					target.off( that.transitionEvent );
3430					target.css( that.vendorPrefix + 'transition', '' );
3431
3432					if ( typeof callback === 'function' ) {
3433						callback();
3434					}
3435				});
3436			} else {
3437				target.stop().animate({ 'opacity': opacity }, this.settings.fadeDuration, function() {
3438					if ( typeof callback === 'function' ) {
3439						callback();
3440					}
3441				});
3442			}
3443		},
3444
3445		// Destroy the module
3446		destroyFade: function() {
3447			this.off( 'update.' + NS );
3448
3449			if ( this.fadeGotoSlideReference !== null ) {
3450				this.gotoSlide = this.fadeGotoSlideReference;
3451			}
3452		},
3453
3454		fadeDefaults: {
3455
3456			// Indicates if fade will be used
3457			fade: false,
3458
3459			// Indicates if the previous slide will be faded out (in addition to the next slide being faded in)
3460			fadeOutPreviousSlide: true,
3461
3462			// Sets the duration of the fade effect
3463			fadeDuration: 500
3464		}
3465	};
3466
3467	$.SliderPro.addModule( 'Fade', Fade );
3468
3469})( window, jQuery );
3470
3471// Touch Swipe module for Slider Pro.
3472// 
3473// Adds touch-swipe functionality for slides.
3474;(function( window, $ ) {
3475
3476	"use strict";
3477	
3478	var NS = 'TouchSwipe.' + $.SliderPro.namespace;
3479
3480	var TouchSwipe = {
3481
3482		// Indicates if touch is supported
3483		isTouchSupport: false,
3484
3485		// The x and y coordinates of the pointer/finger's starting position
3486		touchStartPoint: {x: 0, y: 0},
3487
3488		// The x and y coordinates of the pointer/finger's end position
3489		touchEndPoint: {x: 0, y: 0},
3490
3491		// The distance from the starting to the end position on the x and y axis
3492		touchDistance: {x: 0, y: 0},
3493
3494		// The position of the slides when the touch swipe starts
3495		touchStartPosition: 0,
3496
3497		// Indicates if the slides are being swiped
3498		isTouchMoving: false,
3499
3500		// Stores the names of the events
3501		touchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' },
3502
3503		initTouchSwipe: function() {
3504			var that = this;
3505
3506			// check if touch swipe is enabled
3507			if ( this.settings.touchSwipe === false ) {
3508				return;
3509			}
3510
3511			// check if there is touch support
3512			this.isTouchSupport = 'ontouchstart' in window;
3513
3514			// Get the names of the events
3515			if ( this.isTouchSupport === true ) {
3516				this.touchSwipeEvents.startEvent = 'touchstart';
3517				this.touchSwipeEvents.moveEvent = 'touchmove';
3518				this.touchSwipeEvents.endEvent = 'touchend';
3519			} else {
3520				this.touchSwipeEvents.startEvent = 'mousedown';
3521				this.touchSwipeEvents.moveEvent = 'mousemove';
3522				this.touchSwipeEvents.endEvent = 'mouseup';
3523			}
3524
3525			// Listen for touch swipe/mouse move events
3526			this.$slidesMask.on( this.touchSwipeEvents.startEvent + '.' + NS, $.proxy( this._onTouchStart, this ) );
3527			this.$slidesMask.on( 'dragstart.' + NS, function( event ) {
3528				event.preventDefault();
3529			});
3530
3531			// Add the grabbing icon
3532			this.$slidesMask.addClass( 'sp-grab' );
3533		},
3534
3535		// Called when the slides starts being dragged
3536		_onTouchStart: function( event ) {
3537		
3538			// Disable dragging if the element is set to allow selections
3539			if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) {
3540				return;
3541			}
3542
3543			var that = this,
3544				eventObject = this.isTouchSupport ? event.originalEvent.touches[0] : event.originalEvent;
3545
3546			// Prevent default behavior only for mouse events
3547			if ( this.isTouchSupport === false ) {
3548				event.preventDefault();
3549			}
3550
3551			// Disable click events on links
3552			$( event.target ).parents( '.sp-slide' ).find( 'a' ).one( 'click.' + NS, function( event ) {
3553				event.preventDefault();
3554			});
3555
3556			// Get the initial position of the mouse pointer and the initial position
3557			// of the slides' container
3558			this.touchStartPoint.x = eventObject.pageX || eventObject.clientX;
3559			this.touchStartPoint.y = eventObject.pageY || eventObject.clientY;
3560			this.touchStartPosition = this.slidesPosition;
3561
3562			// Clear the previous distance values
3563			this.touchDistance.x = this.touchDistance.y = 0;
3564
3565			// If the slides are being grabbed while they're still animating, stop the
3566			// current movement
3567			if ( this.$slides.hasClass( 'sp-animated' ) ) {
3568				this.isTouchMoving = true;
3569				this._stopMovement();
3570				this.touchStartPosition = this.slidesPosition;
3571			}
3572
3573			// Listen for move and end events
3574			this.$slidesMask.on( this.touchSwipeEvents.moveEvent + '.' + NS, $.proxy( this._onTouchMove, this ) );
3575			$( document ).on( this.touchSwipeEvents.endEvent + '.' + this.uniqueId + '.' + NS, $.proxy( this._onTouchEnd, this ) );
3576
3577			// Swap grabbing icons
3578			this.$slidesMask.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' );
3579
3580			// Add 'sp-swiping' class to indicate that the slides are being swiped
3581			this.$slider.addClass( 'sp-swiping' );
3582		},
3583
3584		// Called during the slides' dragging
3585		_onTouchMove: function(event) {
3586			var eventObject = this.isTouchSupport ? event.originalEvent.touches[0] : event.originalEvent;
3587
3588			// Indicate that the move event is being fired
3589			this.isTouchMoving = true;
3590
3591			// Get the current position of the mouse pointer
3592			this.touchEndPoint.x = eventObject.pageX || eventObject.clientX;
3593			this.touchEndPoint.y = eventObject.pageY || eventObject.clientY;
3594
3595			// Calculate the distance of the movement on both axis
3596			this.touchDistance.x = this.touchEndPoint.x - this.touchStartPoint.x;
3597			this.touchDistance.y = this.touchEndPoint.y - this.touchStartPoint.y;
3598			
3599			// Calculate the distance of the swipe that takes place in the same direction as the orientation of the slides
3600			// and calculate the distance from the opposite direction.
3601			// 
3602			// For a swipe to be valid there should more distance in the same direction as the orientation of the slides.
3603			var distance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y,
3604				oppositeDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.y : this.touchDistance.x;
3605
3606			// If the movement is in the same direction as the orientation of the slides, the swipe is valid
3607			if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) {
3608				event.preventDefault();
3609			} else {
3610				return;
3611			}
3612
3613			if ( this.settings.loop === false ) {
3614				// Make the slides move slower if they're dragged outside its bounds
3615				if ( ( this.slidesPosition > this.touchStartPosition && this.selectedSlideIndex === 0 ) ||
3616					( this.slidesPosition < this.touchStartPosition && this.selectedSlideIndex === this.getTotalSlides() - 1 )
3617				) {
3618					distance = distance * 0.2;
3619				}
3620			}
3621
3622			this._moveTo( this.touchStartPosition + distance, true );
3623		},
3624
3625		// Called when the slides are released
3626		_onTouchEnd: function( event ) {
3627			var that = this,
3628				touchDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y;
3629
3630			// Remove the move and end listeners
3631			this.$slidesMask.off( this.touchSwipeEvents.moveEvent + '.' + NS );
3632			$( document ).off( this.touchSwipeEvents.endEvent + '.' + this.uniqueId + '.' + NS );
3633
3634			// Swap grabbing icons
3635			this.$slidesMask.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' );
3636
3637			// Check if there is intention for a tap
3638			if ( this.isTouchMoving === false || this.isTouchMoving === true && Math.abs( this.touchDistance.x ) < 10 && Math.abs( this.touchDistance.y ) < 10 ) {
3639				// Re-enable click events on links
3640				$( event.target ).parents( '.sp-slide' ).find( 'a' ).off( 'click.' + NS );
3641				this.$slider.removeClass( 'sp-swiping' );
3642			}
3643
3644			// Remove the 'sp-swiping' class but with a delay
3645			// because there might be other event listeners that check
3646			// the existence of this class, and this class should still be 
3647			// applied for those listeners, since there was a swipe event
3648			setTimeout(function() {
3649				that.$slider.removeClass( 'sp-swiping' );
3650			}, 1);
3651
3652			// Return if the slides didn't move
3653			if ( this.isTouchMoving === false ) {
3654				return;
3655			}
3656
3657			this.isTouchMoving = false;
3658
3659			$( event.target ).parents( '.sp-slide' ).one( 'click', function( event ) {
3660				event.preventDefault();
3661			});
3662
3663			// Calculate the old position of the slides in order to return to it if the swipe
3664			// is below the threshold
3665			var oldSlidesPosition = - parseInt( this.$slides.find( '.sp-slide' ).eq( this.selectedSlideIndex ).css( this.positionProperty ), 10 ) + this.visibleOffset;
3666
3667			if ( Math.abs( touchDistance ) < this.settings.touchSwipeThreshold ) {
3668				this._moveTo( oldSlidesPosition );
3669			} else {
3670
3671				// Calculate by how many slides the slides container has moved
3672				var slideArrayDistance = touchDistance / ( this.slideSize + this.settings.slideDistance );
3673
3674				// Floor the obtained value and add or subtract 1, depending on the direction of the swipe
3675				slideArrayDistance = parseInt( slideArrayDistance, 10 ) + ( slideArrayDistance > 0 ? 1 : - 1 );
3676
3677				// Get the index of the currently selected slide and subtract the position index in order to obtain
3678				// the new index of the selected slide. 
3679				var nextSlideIndex = this.slidesOrder[ $.inArray( this.selectedSlideIndex, this.slidesOrder ) - slideArrayDistance ];
3680
3681				if ( this.settings.loop === true ) {
3682					this.gotoSlide( nextSlideIndex );
3683				} else {
3684					if ( typeof nextSlideIndex !== 'undefined' ) {
3685						this.gotoSlide( nextSlideIndex );
3686					} else {
3687						this._moveTo( oldSlidesPosition );
3688					}
3689				}
3690			}
3691		},
3692
3693		// Destroy the module
3694		destroyTouchSwipe: function() {
3695			this.$slidesMask.off( this.touchSwipeEvents.startEvent + '.' + NS );
3696			this.$slidesMask.off( this.touchSwipeEvents.moveEvent + '.' + NS );
3697			this.$slidesMask.off( 'dragstart.' + NS );
3698			$( document ).off( this.touchSwipeEvents.endEvent + '.' + this.uniqueId + '.' + NS );
3699			this.$slidesMask.removeClass( 'sp-grab' );
3700		},
3701
3702		touchSwipeDefaults: {
3703			
3704			// Indicates whether the touch swipe will be enabled
3705			touchSwipe: true,
3706
3707			// Sets the minimum amount that the slides should move
3708			touchSwipeThreshold: 50
3709		}
3710	};
3711
3712	$.SliderPro.addModule( 'TouchSwipe', TouchSwipe );
3713	
3714})( window, jQuery );
3715
3716// Caption module for Slider Pro.
3717// 
vendor: 66,150 bytes, lines 3718-5904
3718// Adds a corresponding caption for each slide. The caption
3719// will appear and disappear with the slide.
3720;(function( window, $ ) {
3721
3722	"use strict";
3723	
3724	var NS = 'Caption.' + $.SliderPro.namespace;
3725
3726	var Caption = {
3727
3728		// Reference to the container element that will hold the caption
3729		$captionContainer: null,
3730
3731		// The caption content/text
3732		captionContent: '',
3733
3734		initCaption: function() {
3735			this.on( 'update.' + NS, $.proxy( this._captionOnUpdate, this ) );
3736			this.on( 'gotoSlide.' + NS, $.proxy( this._updateCaptionContent, this ) );
3737		},
3738
3739		// Create the caption container and hide the captions inside the slides
3740		_captionOnUpdate: function() {
3741			this.$captionContainer = this.$slider.find( '.sp-caption-container' );
3742
3743			if ( this.$slider.find( '.sp-caption' ).length && this.$captionContainer.length === 0 ) {
3744				this.$captionContainer = $( '<div class="sp-caption-container"></div>' ).appendTo( this.$slider );
3745
3746				// Show the caption for the selected slide
3747				this._updateCaptionContent();
3748			}
3749
3750			// Hide the captions inside the slides
3751			this.$slides.find( '.sp-caption' ).each(function() {
3752				$( this ).css( 'display', 'none' );
3753			});
3754		},
3755
3756		// Show the caption content for the selected slide
3757		_updateCaptionContent: function() {
3758			var that = this,
3759				newCaptionField = this.$slider.find( '.sp-slide' ).eq( this.selectedSlideIndex ).find( '.sp-caption' ),
3760				newCaptionContent = newCaptionField.length !== 0 ? newCaptionField.html() : '';
3761
3762			// Either use a fade effect for swapping the captions or use an instant change
3763			if ( this.settings.fadeCaption === true ) {
3764				
3765				// If the previous slide had a caption, fade out that caption first and when the animation is over
3766				// fade in the current caption.
3767				// If the previous slide didn't have a caption, fade in the current caption directly.
3768				if ( this.captionContent !== '' ) {
3769
3770					// If the caption container has 0 opacity when the fade out transition starts, set it
3771					// to 1 because the transition wouldn't work if the initial and final values are the same,
3772					// and the callback functions wouldn't fire in this case.
3773					if ( parseFloat( this.$captionContainer.css( 'opacity' ), 10 ) === 0 ) {
3774						this.$captionContainer.css( this.vendorPrefix + 'transition', '' );
3775						this.$captionContainer.css( 'opacity', 1 );
3776					}
3777
3778					this._fadeCaptionTo( 0, function() {
3779						that.captionContent = newCaptionContent;
3780
3781						if ( newCaptionContent !== '' ) {
3782							that.$captionContainer.html( that.captionContent );
3783							that._fadeCaptionTo( 1 );
3784						} else {
3785							that.$captionContainer.empty();
3786						}
3787					});
3788				} else {
3789					this.captionContent = newCaptionContent;
3790					this.$captionContainer.html( this.captionContent );
3791					this.$captionContainer.css( 'opacity', 0 );
3792					this._fadeCaptionTo( 1 );
3793				}
3794			} else {
3795				this.captionContent = newCaptionContent;
3796				this.$captionContainer.html( this.captionContent );
3797			}
3798		},
3799
3800		// Fade the caption container to the specified opacity
3801		_fadeCaptionTo: function( opacity, callback ) {
3802			var that = this;
3803
3804			// Use CSS transitions if they are supported. If not, use JavaScript animation.
3805			if ( this.supportedAnimation === 'css-3d' || this.supportedAnimation === 'css-2d' ) {
3806				
3807				// There needs to be a delay between the moment the opacity is set
3808				// and the moment the transitions starts.
3809				setTimeout(function(){
3810					var css = { 'opacity': opacity };
3811					css[ that.vendorPrefix + 'transition' ] = 'opacity ' + that.settings.captionFadeDuration / 1000 + 's';
3812					that.$captionContainer.css( css );
3813				}, 1 );
3814
3815				this.$captionContainer.on( this.transitionEvent, function( event ) {
3816					if ( event.target !== event.currentTarget ) {
3817						return;
3818					}
3819
3820					that.$captionContainer.off( that.transitionEvent );
3821					that.$captionContainer.css( that.vendorPrefix + 'transition', '' );
3822
3823					if ( typeof callback === 'function' ) {
3824						callback();
3825					}
3826				});
3827			} else {
3828				this.$captionContainer.stop().animate({ 'opacity': opacity }, this.settings.captionFadeDuration, function() {
3829					if ( typeof callback === 'function' ) {
3830						callback();
3831					}
3832				});
3833			}
3834		},
3835
3836		// Destroy the module
3837		destroyCaption: function() {
3838			this.off( 'update.' + NS );
3839			this.off( 'gotoSlide.' + NS );
3840
3841			this.$captionContainer.remove();
3842
3843			this.$slider.find( '.sp-caption' ).each(function() {
3844				$( this ).css( 'display', '' );
3845			});
3846		},
3847
3848		captionDefaults: {
3849
3850			// Indicates whether or not the captions will be faded
3851			fadeCaption: true,
3852
3853			// Sets the duration of the fade animation
3854			captionFadeDuration: 500
3855		}
3856	};
3857
3858	$.SliderPro.addModule( 'Caption', Caption );
3859	
3860})( window, jQuery );
3861
3862// Deep Linking module for Slider Pro.
3863// 
3864// Updates the hash of the URL as the user navigates through the slides.
3865// Also, allows navigating to a specific slide by indicating it in the hash.
3866;(function( window, $ ) {
3867
3868	"use strict";
3869
3870	var NS = 'DeepLinking.' + $.SliderPro.namespace;
3871
3872	var DeepLinking = {
3873
3874		initDeepLinking: function() {
3875			var that = this,
3876
3877				// Use this variable as a flag to prevent the slider to call 'gotoSlide' after a hash update
3878				// if that hash update was triggered by another 'gotoSlide' call.
3879				allowGotoHash = true;
3880
3881			// Parse the initial hash
3882			this.on( 'init.' + NS, function() {
3883				that._gotoHash( window.location.hash );
3884			});
3885
3886			// Update the hash when a new slide is selected
3887			this.on( 'gotoSlide.' + NS, function( event ) {
3888				allowGotoHash = false;
3889
3890				if ( that.settings.updateHash === true ) {
3891					window.location.hash = that.$slider.attr( 'id' ) + '/' + event.index;
3892				}
3893			});
3894
3895			// Check when the hash changes and navigate to the indicated slide
3896			$( window ).on( 'hashchange.' + this.uniqueId + '.' + NS, function() {
3897				if ( allowGotoHash === true ) {
3898					that._gotoHash( window.location.hash );
3899				}
3900				
3901				allowGotoHash = true;
3902			});
3903		},
3904
3905		// Parse the hash and return the slider id and the slide id
3906		_parseHash: function( hash ) {
3907			if ( hash !== '' ) {
3908				// Eliminate the # symbol
3909				hash = hash.substring(1);
3910
3911				// Get the specified slider id and slide id
3912				var values = hash.split( '/' ),
3913					slideId = values.pop(),
3914					sliderId = hash.slice( 0, - slideId.toString().length - 1 );
3915
3916				if ( this.$slider.attr( 'id' ) === sliderId ) {
3917					return { 'sliderID': sliderId, 'slideId': slideId };
3918				}
3919			}
3920
3921			return false;
3922		},
3923
3924		// Navigate to the appropriate slide, based on the specified hash
3925		_gotoHash: function( hash ) {
3926			var result = this._parseHash( hash );
3927
3928			if ( result === false ) {
3929				return;
3930			}
3931
3932			var slideId = result.slideId,
3933				slideIdNumber = parseInt( slideId, 10 );
3934
3935			// check if the specified slide id is a number or string
3936			if ( isNaN( slideIdNumber ) ) {
3937				// get the index of the slide based on the specified id
3938				var slideIndex = this.$slider.find( '.sp-slide#' + slideId ).index();
3939
3940				if ( slideIndex !== -1 ) {
3941					this.gotoSlide( slideIndex );
3942				}
3943			} else {
3944				this.gotoSlide( slideIdNumber );
3945			}
3946		},
3947
3948		// Destroy the module
3949		destroyDeepLinking: function() {
3950			this.off( 'init.' + NS );
3951			this.off( 'gotoSlide.' + NS );
3952			$( window ).off( 'hashchange.' + this.uniqueId + '.' + NS );
3953		},
3954
3955		deepLinkingDefaults: {
3956
3957			// Indicates whether the hash will be updated when a new slide is selected
3958			updateHash: false
3959		}
3960	};
3961
3962	$.SliderPro.addModule( 'DeepLinking', DeepLinking );
3963	
3964})( window, jQuery );
3965
3966// Autoplay module for Slider Pro.
3967// 
3968// Adds automatic navigation through the slides by calling the
3969// 'nextSlide' or 'previousSlide' methods at certain time intervals.
3970;(function( window, $ ) {
3971
3972	"use strict";
3973	
3974	var NS = 'Autoplay.' + $.SliderPro.namespace;
3975
3976	var Autoplay = {
3977
3978		autoplayTimer: null,
3979
3980		isTimerRunning: false,
3981
3982		isTimerPaused: false,
3983
3984		initAutoplay: function() {
3985			this.on( 'update.' + NS, $.proxy( this._autoplayOnUpdate, this ) );
3986		},
3987
3988		// Start the autoplay if it's enabled, or stop it if it's disabled but running 
3989		_autoplayOnUpdate: function( event ) {
3990			if ( this.settings.autoplay === true ) {
3991				this.on( 'gotoSlide.' + NS, $.proxy( this._autoplayOnGotoSlide, this ) );
3992				this.on( 'mouseenter.' + NS, $.proxy( this._autoplayOnMouseEnter, this ) );
3993				this.on( 'mouseleave.' + NS, $.proxy( this._autoplayOnMouseLeave, this ) );
3994
3995				this.startAutoplay();
3996			} else {
3997				this.off( 'gotoSlide.' + NS );
3998				this.off( 'mouseenter.' + NS );
3999				this.off( 'mouseleave.' + NS );
4000
4001				this.stopAutoplay();
4002			}
4003		},
4004
4005		// Restart the autoplay timer when a new slide is selected
4006		_autoplayOnGotoSlide: function( event ) {
4007			// stop previous timers before starting a new one
4008			if ( this.isTimerRunning === true ) {
4009				this.stopAutoplay();
4010			}
4011			
4012			if ( this.isTimerPaused === false ) {
4013				this.startAutoplay();
4014			}
4015		},
4016
4017		// Pause the autoplay when the slider is hovered
4018		_autoplayOnMouseEnter: function( event ) {
4019			if ( this.isTimerRunning && ( this.settings.autoplayOnHover === 'pause' || this.settings.autoplayOnHover === 'stop' ) ) {
4020				this.stopAutoplay();
4021				this.isTimerPaused = true;
4022			}
4023		},
4024
4025		// Start the autoplay when the mouse moves away from the slider
4026		_autoplayOnMouseLeave: function( event ) {
4027			if ( this.settings.autoplay === true && this.isTimerRunning === false && this.settings.autoplayOnHover !== 'stop' ) {
4028				this.startAutoplay();
4029				this.isTimerPaused = false;
4030			}
4031		},
4032
4033		// Starts the autoplay
4034		startAutoplay: function() {
4035			var that = this;
4036			
4037			this.isTimerRunning = true;
4038
4039			this.autoplayTimer = setTimeout(function() {
4040				if ( that.settings.autoplayDirection === 'normal' ) {
4041					that.nextSlide();
4042				} else if ( that.settings.autoplayDirection === 'backwards' ) {
4043					that.previousSlide();
4044				}
4045			}, this.settings.autoplayDelay );
4046		},
4047
4048		// Stops the autoplay
4049		stopAutoplay: function() {
4050			this.isTimerRunning = false;
4051
4052			clearTimeout( this.autoplayTimer );
4053		},
4054
4055		// Destroy the module
4056		destroyAutoplay: function() {
4057			clearTimeout( this.autoplayTimer );
4058
4059			this.off( 'update.' + NS );
4060			this.off( 'gotoSlide.' + NS );
4061			this.off( 'mouseenter.' + NS );
4062			this.off( 'mouseleave.' + NS );
4063		},
4064
4065		autoplayDefaults: {
4066			// Indicates whether or not autoplay will be enabled
4067			autoplay: true,
4068
4069			// Sets the delay/interval at which the autoplay will run
4070			autoplayDelay: 5000,
4071
4072			// Indicates whether autoplay will navigate to the next slide or previous slide
4073			autoplayDirection: 'normal',
4074
4075			// Indicates if the autoplay will be paused or stopped when the slider is hovered.
4076			// Possible values are 'pause', 'stop' or 'none'.
4077			autoplayOnHover: 'pause'
4078		}
4079	};
4080
4081	$.SliderPro.addModule( 'Autoplay', Autoplay );
4082	
4083})(window, jQuery);
4084
4085// Keyboard module for Slider Pro.
4086// 
4087// Adds the possibility to navigate through slides using the keyboard arrow keys, or
4088// open the link attached to the main slide image by using the Enter key.
4089;(function( window, $ ) {
4090
4091	"use strict";
4092	
4093	var NS = 'Keyboard.' + $.SliderPro.namespace;
4094
4095	var Keyboard = {
4096
4097		initKeyboard: function() {
4098			var that = this,
4099				hasFocus = false;
4100
4101			if ( this.settings.keyboard === false ) {
4102				return;
4103			}
4104
4105			// Detect when the slide is in focus and when it's not, and, optionally, make it
4106			// responsive to keyboard input only when it's in focus
4107			this.$slider.on( 'focus.' + NS, function() {
4108				hasFocus = true;
4109			});
4110
4111			this.$slider.on( 'blur.' + NS, function() {
4112				hasFocus = false;
4113			});
4114
4115			$( document ).on( 'keydown.' + this.uniqueId + '.' + NS, function( event ) {
4116				if ( that.settings.keyboardOnlyOnFocus === true && hasFocus === false ) {
4117					return;
4118				}
4119
4120				// If the left arrow key is pressed, go to the previous slide.
4121				// If the right arrow key is pressed, go to the next slide.
4122				// If the Enter key is pressed, open the link attached to the main slide image.
4123				if ( event.which === 37 ) {
4124					that.previousSlide();
4125				} else if ( event.which === 39 ) {
4126					that.nextSlide();
4127				} else if ( event.which === 13 ) {
4128					that.$slider.find( '.sp-slide' ).eq( that.selectedSlideIndex ).find( '.sp-image-container a' )[0].click();
4129				}
4130			});
4131		},
4132
4133		// Destroy the module
4134		destroyKeyboard: function() {
4135			this.$slider.off( 'focus.' + NS );
4136			this.$slider.off( 'blur.' + NS );
4137			$( document ).off( 'keydown.' + this.uniqueId + '.' + NS );
4138		},
4139
4140		keyboardDefaults: {
4141
4142			// Indicates whether keyboard navigation will be enabled
4143			keyboard: true,
4144
4145			// Indicates whether the slider will respond to keyboard input only when
4146			// the slider is in focus.
4147			keyboardOnlyOnFocus: false
4148		}
4149	};
4150
4151	$.SliderPro.addModule( 'Keyboard', Keyboard );
4152	
4153})( window, jQuery );
4154
4155// Full Screen module for Slider Pro.
4156// 
4157// Adds the possibility to open the slider full-screen, using the HMTL5 FullScreen API.
4158;(function( window, $ ) {
4159
4160	"use strict";
4161
4162	var NS = 'FullScreen.' + $.SliderPro.namespace;
4163
4164	var FullScreen = {
4165
4166		// Indicates whether the slider is currently in full-screen mode
4167		isFullScreen: false,
4168
4169		// Reference to the full-screen button
4170		$fullScreenButton: null,
4171
4172		// Reference to a set of settings that influence the slider's size
4173		// before it goes full-screen
4174		sizeBeforeFullScreen: {},
4175
4176		initFullScreen: function() {
4177			if ( ! ( document.fullscreenEnabled ||
4178				document.webkitFullscreenEnabled ||
4179				document.mozFullScreenEnabled ||
4180				document.msFullscreenEnabled ) ) {
4181				return;
4182			}
4183		
4184			this.on( 'update.' + NS, $.proxy( this._fullScreenOnUpdate, this ) );
4185		},
4186
4187		// Create or remove the full-screen button depending on the value of the 'fullScreen' option
4188		_fullScreenOnUpdate: function() {
4189			if ( this.settings.fullScreen === true && this.$fullScreenButton === null ) {
4190				this._addFullScreen();
4191			} else if ( this.settings.fullScreen === false && this.$fullScreenButton !== null ) {
4192				this._removeFullScreen();
4193			}
4194
4195			if ( this.settings.fullScreen === true ) {
4196				if ( this.settings.fadeFullScreen === true ) {
4197					this.$fullScreenButton.addClass( 'sp-fade-full-screen' );
4198				} else if ( this.settings.fadeFullScreen === false ) {
4199					this.$fullScreenButton.removeClass( 'sp-fade-full-screen' );
4200				}
4201			}
4202		},
4203
4204		// Create the full-screen button
4205		_addFullScreen: function() {
4206			this.$fullScreenButton = $('<div class="sp-full-screen-button"></div>').appendTo( this.$slider );
4207			this.$fullScreenButton.on( 'click.' + NS, $.proxy( this._onFullScreenButtonClick, this ) );
4208
4209			document.addEventListener( 'fullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4210			document.addEventListener( 'mozfullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4211			document.addEventListener( 'webkitfullscreenchange', $.proxy( this._onFullScreenChange, this ) );
4212			document.addEventListener( 'MSFullscreenChange', $.proxy( this._onFullScreenChange, this ) );
4213		},
4214
4215		// Remove the full-screen button
4216		_removeFullScreen: function() {
4217			if ( this.$fullScreenButton !== null ) {
4218				this.$fullScreenButton.off( 'click.' + NS );
4219				this.$fullScreenButton.remove();
4220				this.$fullScreenButton = null;
4221				document.removeEventListener( 'fullscreenchange', this._onFullScreenChange );
4222				document.removeEventListener( 'mozfullscreenchange', this._onFullScreenChange );
4223				document.removeEventListener( 'webkitfullscreenchange', this._onFullScreenChange );
4224				document.removeEventListener( 'MSFullscreenChange', this._onFullScreenChange );
4225			}
4226		},
4227
4228		// When the full-screen button is clicked, put the slider into full-screen mode, and
4229		// take it out of the full-screen mode when it's clicked again.
4230		_onFullScreenButtonClick: function() {
4231			if ( this.isFullScreen === false ) {
4232				if ( this.instance.requestFullScreen ) {
4233					this.instance.requestFullScreen();
4234				} else if ( this.instance.mozRequestFullScreen ) {
4235					this.instance.mozRequestFullScreen();
4236				} else if ( this.instance.webkitRequestFullScreen ) {
4237					this.instance.webkitRequestFullScreen();
4238				} else if ( this.instance.msRequestFullscreen ) {
4239					this.instance.msRequestFullscreen();
4240				}
4241			} else {
4242				if ( document.exitFullScreen ) {
4243					document.exitFullScreen();
4244				} else if ( document.mozCancelFullScreen ) {
4245					document.mozCancelFullScreen();
4246				} else if ( document.webkitCancelFullScreen ) {
4247					document.webkitCancelFullScreen();
4248				} else if ( document.msExitFullscreen ) {
4249					document.msExitFullscreen();
4250				}
4251			}
4252		},
4253
4254		// This will be called whenever the full-screen mode changes.
4255		// If the slider is in full-screen mode, set it to 'full window', and if it's
4256		// not in full-screen mode anymore, set it back to the original size.
4257		_onFullScreenChange: function() {
4258			this.isFullScreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement ? true : false;
4259
4260			if ( this.isFullScreen === true ) {
4261				this.sizeBeforeFullScreen = { forceSize: this.settings.forceSize, autoHeight: this.settings.autoHeight };
4262				this.$slider.addClass( 'sp-full-screen' );
4263				this.settings.forceSize = 'fullWindow';
4264				this.settings.autoHeight = false;
4265			} else {
4266				this.$slider.css( 'margin', '' );
4267				this.$slider.removeClass( 'sp-full-screen' );
4268				this.settings.forceSize = this.sizeBeforeFullScreen.forceSize;
4269				this.settings.autoHeight = this.sizeBeforeFullScreen.autoHeight;
4270			}
4271
4272			this.resize();
4273		},
4274
4275		// Destroy the module
4276		destroyFullScreen: function() {
4277			this.off( 'update.' + NS );
4278			this._removeFullScreen();
4279		},
4280
4281		fullScreenDefaults: {
4282
4283			// Indicates whether the full-screen button is enabled
4284			fullScreen: false,
4285
4286			// Indicates whether the button will fade in only on hover
4287			fadeFullScreen: true
4288		}
4289	};
4290
4291	$.SliderPro.addModule( 'FullScreen', FullScreen );
4292
4293})( window, jQuery );
4294
4295// Buttons module for Slider Pro.
4296// 
4297// Adds navigation buttons at the bottom of the slider.
4298;(function( window, $ ) {
4299
4300	"use strict";
4301	
4302	var NS = 'Buttons.' + $.SliderPro.namespace;
4303
4304	var Buttons = {
4305
4306		// Reference to the buttons container
4307		$buttons: null,
4308
4309		initButtons: function() {
4310			this.on( 'update.' + NS, $.proxy( this._buttonsOnUpdate, this ) );
4311		},
4312
4313		_buttonsOnUpdate: function() {
4314			this.$buttons = this.$slider.find('.sp-buttons');
4315			
4316			// If there is more that one slide but the buttons weren't created yet, create the buttons.
4317			// If the buttons were created but their number differs from the total number of slides, re-create the buttons.
4318			// If the buttons were created but there are less than one slide, remove the buttons.s
4319			if ( this.settings.buttons === true && this.getTotalSlides() > 1 && this.$buttons.length === 0 ) {
4320				this._createButtons();
4321			} else if ( this.settings.buttons === true && this.getTotalSlides() !== this.$buttons.find( '.sp-button' ).length && this.$buttons.length !== 0 ) {
4322				this._adjustButtons();
4323			} else if ( this.settings.buttons === false || ( this.getTotalSlides() <= 1 && this.$buttons.length !== 0 ) ) {
4324				this._removeButtons();
4325			}
4326		},
4327
4328		// Create the buttons
4329		_createButtons: function() {
4330			var that = this;
4331
4332			// Create the buttons' container
4333			this.$buttons = $( '<div class="sp-buttons"></div>' ).appendTo( this.$slider );
4334
4335			// Create the buttons
4336			for ( var i = 0; i < this.getTotalSlides(); i++ ) {
4337				$( '<div class="sp-button"></div>' ).appendTo( this.$buttons );
4338			}
4339
4340			// Listen for button clicks 
4341			this.$buttons.on( 'click.' + NS, '.sp-button', function() {
4342				that.gotoSlide( $( this ).index() );
4343			});
4344
4345			// Set the initially selected button
4346			this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' );
4347
4348			// Select the corresponding button when the slide changes
4349			this.on( 'gotoSlide.' + NS, function( event ) {
4350				that.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' );
4351				that.$buttons.find( '.sp-button' ).eq( event.index ).addClass( 'sp-selected-button' );
4352			});
4353
4354			// Indicate that the slider has buttons 
4355			this.$slider.addClass( 'sp-has-buttons' );
4356		},
4357
4358		// Re-create the buttons. This is calles when the number of slides changes.
4359		_adjustButtons: function() {
4360			this.$buttons.empty();
4361
4362			// Create the buttons
4363			for ( var i = 0; i < this.getTotalSlides(); i++ ) {
4364				$( '<div class="sp-button"></div>' ).appendTo( this.$buttons );
4365			}
4366
4367			// Change the selected the buttons
4368			this.$buttons.find( '.sp-selected-button' ).removeClass( 'sp-selected-button' );
4369			this.$buttons.find( '.sp-button' ).eq( this.selectedSlideIndex ).addClass( 'sp-selected-button' );
4370		},
4371
4372		// Remove the buttons
4373		_removeButtons: function() {
4374			this.$buttons.off( 'click.' + NS, '.sp-button' );
4375			this.off( 'gotoSlide.' + NS );
4376			this.$buttons.remove();
4377			this.$slider.removeClass( 'sp-has-buttons' );
4378		},
4379
4380		destroyButtons: function() {
4381			this._removeButtons();
4382			this.off( 'update.' + NS );
4383		},
4384
4385		buttonsDefaults: {
4386			
4387			// Indicates whether the buttons will be created
4388			buttons: true
4389		}
4390	};
4391
4392	$.SliderPro.addModule( 'Buttons', Buttons );
4393
4394})( window, jQuery );
4395
4396// Arrows module for Slider Pro.
4397// 
4398// Adds arrows for navigating to the next or previous slide.
4399;(function( window, $ ) {
4400
4401	"use strict";
4402
4403	var NS = 'Arrows.' + $.SliderPro.namespace;
4404
4405	var Arrows = {
4406
4407		// Reference to the arrows container
4408		$arrows: null,
4409
4410		// Reference to the previous arrow
4411		$previousArrow: null,
4412
4413		// Reference to the next arrow
4414		$nextArrow: null,
4415
4416		initArrows: function() {
4417			this.on( 'update.' + NS, $.proxy( this._arrowsOnUpdate, this ) );
4418			this.on( 'gotoSlide.' + NS, $.proxy( this._checkArrowsVisibility, this ) );
4419		},
4420
4421		_arrowsOnUpdate: function() {
4422			var that = this;
4423
4424			// Create the arrows if the 'arrows' option is set to true
4425			if ( this.settings.arrows === true && this.$arrows === null ) {
4426				this.$arrows = $( '<div class="sp-arrows"></div>' ).appendTo( this.$slidesContainer );
4427				
4428				this.$previousArrow = $( '<div class="sp-arrow sp-previous-arrow"></div>' ).appendTo( this.$arrows );
4429				this.$nextArrow = $( '<div class="sp-arrow sp-next-arrow"></div>' ).appendTo( this.$arrows );
4430
4431				this.$previousArrow.on( 'click.' + NS, function() {
4432					that.previousSlide();
4433				});
4434
4435				this.$nextArrow.on( 'click.' + NS, function() {
4436					that.nextSlide();
4437				});
4438
4439				this._checkArrowsVisibility();
4440			} else if ( this.settings.arrows === false && this.$arrows !== null ) {
4441				this._removeArrows();
4442			}
4443
4444			if ( this.settings.arrows === true ) {
4445				if ( this.settings.fadeArrows === true ) {
4446					this.$arrows.addClass( 'sp-fade-arrows' );
4447				} else if ( this.settings.fadeArrows === false ) {
4448					this.$arrows.removeClass( 'sp-fade-arrows' );
4449				}
4450			}
4451		},
4452
4453		// Show or hide the arrows depending on the position of the selected slide
4454		_checkArrowsVisibility: function() {
4455			if ( this.settings.arrows === false || this.settings.loop === true ) {
4456				return;
4457			}
4458
4459			if ( this.selectedSlideIndex === 0 ) {
4460				this.$previousArrow.css( 'display', 'none' );
4461			} else {
4462				this.$previousArrow.css( 'display', 'block' );
4463			}
4464
4465			if ( this.selectedSlideIndex === this.getTotalSlides() - 1 ) {
4466				this.$nextArrow.css( 'display', 'none' );
4467			} else {
4468				this.$nextArrow.css( 'display', 'block' );
4469			}
4470		},
4471		
4472		_removeArrows: function() {
4473			if ( this.$arrows !== null ) {
4474				this.$previousArrow.off( 'click.' + NS );
4475				this.$nextArrow.off( 'click.' + NS );
4476				this.$arrows.remove();
4477				this.$arrows = null;
4478			}
4479		},
4480
4481		destroyArrows: function() {
4482			this._removeArrows();
4483			this.off( 'update.' + NS );
4484			this.off( 'gotoSlide.' + NS );
4485		},
4486
4487		arrowsDefaults: {
4488
4489			// Indicates whether the arrow buttons will be created
4490			arrows: false,
4491
4492			// Indicates whether the arrows will fade in only on hover
4493			fadeArrows: true
4494		}
4495	};
4496
4497	$.SliderPro.addModule( 'Arrows', Arrows );
4498
4499})( window, jQuery );
4500
4501// Thumbnail Touch Swipe module for Slider Pro.
4502// 
4503// Adds touch-swipe functionality for thumbnails.
4504;(function( window, $ ) {
4505
4506	"use strict";
4507	
4508	var NS = 'ThumbnailTouchSwipe.' + $.SliderPro.namespace;
4509
4510	var ThumbnailTouchSwipe = {
4511
4512		// Indicates if touch is supported
4513		isThumbnailTouchSupport: false,
4514
4515		// The x and y coordinates of the pointer/finger's starting position
4516		thumbnailTouchStartPoint: { x: 0, y: 0 },
4517
4518		// The x and y coordinates of the pointer/finger's end position
4519		thumbnailTouchEndPoint: { x: 0, y: 0 },
4520
4521		// The distance from the starting to the end position on the x and y axis
4522		thumbnailTouchDistance: { x: 0, y: 0 },
4523
4524		// The position of the thumbnail scroller when the touch swipe starts
4525		thumbnailTouchStartPosition: 0,
4526
4527		// Indicates if the thumbnail scroller is being swiped
4528		isThumbnailTouchMoving: false,
4529
4530		// Indicates if the touch swipe was initialized
4531		isThumbnailTouchSwipe: false,
4532
4533		// Stores the names of the events
4534		thumbnailTouchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' },
4535
4536		initThumbnailTouchSwipe: function() {
4537			this.on( 'update.' + NS, $.proxy( this._thumbnailTouchSwipeOnUpdate, this ) );
4538		},
4539
4540		_thumbnailTouchSwipeOnUpdate: function() {
4541
4542			// Return if there are no thumbnails
4543			if ( this.isThumbnailScroller === false ) {
4544				return;
4545			}
4546
4547			// Initialize the touch swipe functionality if it wasn't initialized yet
4548			if ( this.settings.thumbnailTouchSwipe === true && this.isThumbnailTouchSwipe === false ) {
4549				this.isThumbnailTouchSwipe = true;
4550
4551				// Check if there is touch support
4552				this.isThumbnailTouchSupport = 'ontouchstart' in window;
4553
4554				// Get the names of the events
4555				if ( this.isThumbnailTouchSupport === true ) {
4556					this.thumbnailTouchSwipeEvents.startEvent = 'touchstart';
4557					this.thumbnailTouchSwipeEvents.moveEvent = 'touchmove';
4558					this.thumbnailTouchSwipeEvents.endEvent = 'touchend';
4559				} else {
4560					this.thumbnailTouchSwipeEvents.startEvent = 'mousedown';
4561					this.thumbnailTouchSwipeEvents.moveEvent = 'mousemove';
4562					this.thumbnailTouchSwipeEvents.endEvent = 'mouseup';
4563				}
4564				
4565				// Listen for touch swipe/mouse move events
4566				this.$thumbnails.on( this.thumbnailTouchSwipeEvents.startEvent + '.' + NS, $.proxy( this._onThumbnailTouchStart, this ) );
4567				this.$thumbnails.on( 'dragstart.' + NS, function( event ) {
4568					event.preventDefault();
4569				});
4570			
4571				// Add the grabbing icon
4572				this.$thumbnails.addClass( 'sp-grab' );
4573			}
4574
4575			// Remove the default thumbnailClick
4576			$.each( this.thumbnails, function( index, thumbnail ) {
4577				thumbnail.off( 'thumbnailClick' );
4578			});
4579		},
4580
4581		// Called when the thumbnail scroller starts being dragged
4582		_onThumbnailTouchStart: function( event ) {
4583			// Disable dragging if the element is set to allow selections
4584			if ( $( event.target ).closest( '.sp-selectable' ).length >= 1 ) {
4585				return;
4586			}
4587
4588			var that = this,
4589				eventObject = this.isThumbnailTouchSupport ? event.originalEvent.touches[0] : event.originalEvent;
4590
4591			// Prevent default behavior for mouse events
4592			if ( this.isThumbnailTouchSupport === false ) {
4593				event.preventDefault();
4594			}
4595
4596			// Disable click events on links
4597			$( event.target ).parents( '.sp-thumbnail-container' ).find( 'a' ).one( 'click.' + NS, function( event ) {
4598				event.preventDefault();
4599			});
4600
4601			// Get the initial position of the mouse pointer and the initial position
4602			// of the thumbnail scroller
4603			this.thumbnailTouchStartPoint.x = eventObject.pageX || eventObject.clientX;
4604			this.thumbnailTouchStartPoint.y = eventObject.pageY || eventObject.clientY;
4605			this.thumbnailTouchStartPosition = this.thumbnailsPosition;
4606
4607			// Clear the previous distance values
4608			this.thumbnailTouchDistance.x = this.thumbnailTouchDistance.y = 0;
4609
4610			// If the thumbnail scroller is being grabbed while it's still animating, stop the
4611			// current movement
4612			if ( this.$thumbnails.hasClass( 'sp-animated' ) ) {
4613				this.isThumbnailTouchMoving = true;
4614				this._stopThumbnailsMovement();
4615				this.thumbnailTouchStartPosition = this.thumbnailsPosition;
4616			}
4617
4618			// Listen for move and end events
4619			this.$thumbnails.on( this.thumbnailTouchSwipeEvents.moveEvent + '.' + NS, $.proxy( this._onThumbnailTouchMove, this ) );
4620			$( document ).on( this.thumbnailTouchSwipeEvents.endEvent + '.' + this.uniqueId + '.' + NS, $.proxy( this._onThumbnailTouchEnd, this ) );
4621
4622			// Swap grabbing icons
4623			this.$thumbnails.removeClass( 'sp-grab' ).addClass( 'sp-grabbing' );
4624
4625			// Add 'sp-swiping' class to indicate that the thumbnail scroller is being swiped
4626			this.$thumbnailsContainer.addClass( 'sp-swiping' );
4627		},
4628
4629		// Called during the thumbnail scroller's dragging
4630		_onThumbnailTouchMove: function(event) {
4631			var eventObject = this.isThumbnailTouchSupport ? event.originalEvent.touches[0] : event.originalEvent;
4632
4633			// Indicate that the move event is being fired
4634			this.isThumbnailTouchMoving = true;
4635
4636			// Get the current position of the mouse pointer
4637			this.thumbnailTouchEndPoint.x = eventObject.pageX || eventObject.clientX;
4638			this.thumbnailTouchEndPoint.y = eventObject.pageY || eventObject.clientY;
4639
4640			// Calculate the distance of the movement on both axis
4641			this.thumbnailTouchDistance.x = this.thumbnailTouchEndPoint.x - this.thumbnailTouchStartPoint.x;
4642			this.thumbnailTouchDistance.y = this.thumbnailTouchEndPoint.y - this.thumbnailTouchStartPoint.y;
4643			
4644			// Calculate the distance of the swipe that takes place in the same direction as the orientation of the thumbnails
4645			// and calculate the distance from the opposite direction.
4646			// 
4647			// For a swipe to be valid there should more distance in the same direction as the orientation of the thumbnails.
4648			var distance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y,
4649				oppositeDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.y : this.thumbnailTouchDistance.x;
4650
4651			// If the movement is in the same direction as the orientation of the thumbnails, the swipe is valid
4652			if ( Math.abs( distance ) > Math.abs( oppositeDistance ) ) {
4653				event.preventDefault();
4654			} else {
4655				return;
4656			}
4657
4658			// Make the thumbnail scroller move slower if it's dragged outside its bounds
4659			if ( this.thumbnailsPosition >= 0 ) {
4660				var infOffset = - this.thumbnailTouchStartPosition;
4661				distance = infOffset + ( distance - infOffset ) * 0.2;
4662			} else if ( this.thumbnailsPosition <= - this.thumbnailsSize + this.thumbnailsContainerSize ) {
4663				var supOffset = this.thumbnailsSize - this.thumbnailsContainerSize + this.thumbnailTouchStartPosition;
4664				distance = - supOffset + ( distance + supOffset ) * 0.2;
4665			}
4666			
4667			this._moveThumbnailsTo( this.thumbnailTouchStartPosition + distance, true );
4668		},
4669
4670		// Called when the thumbnail scroller is released
4671		_onThumbnailTouchEnd: function( event ) {
4672			var that = this,
4673				thumbnailTouchDistance = this.thumbnailsOrientation === 'horizontal' ? this.thumbnailTouchDistance.x : this.thumbnailTouchDistance.y;
4674
4675			// Remove the move and end listeners
4676			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent + '.' + NS );
4677			$( document ).off( this.thumbnailTouchSwipeEvents.endEvent + '.' + this.uniqueId + '.' + NS );
4678
4679			// Swap grabbing icons
4680			this.$thumbnails.removeClass( 'sp-grabbing' ).addClass( 'sp-grab' );
4681
4682			// Check if there is intention for a tap/click
4683			if ( this.isThumbnailTouchMoving === false ||
4684				this.isThumbnailTouchMoving === true &&
4685				Math.abs( this.thumbnailTouchDistance.x ) < 10 &&
4686				Math.abs( this.thumbnailTouchDistance.y ) < 10
4687			) {
4688				var targetThumbnail = $( event.target ).hasClass( 'sp-thumbnail-container' ) ? $( event.target ) : $( event.target ).parents( '.sp-thumbnail-container' ),
4689					index = targetThumbnail.index();
4690
4691				// If a link is cliked, navigate to that link, else navigate to the slide that corresponds to the thumbnail
4692				if ( $( event.target ).parents( 'a' ).length !== 0 ) {
4693					$( event.target ).parents( 'a' ).off( 'click.' + NS );
4694					this.$thumbnailsContainer.removeClass( 'sp-swiping' );
4695				} else if ( index !== this.selectedThumbnailIndex && index !== -1 ) {
4696					this.gotoSlide( index );
4697				}
4698
4699				return;
4700			}
4701
4702			this.isThumbnailTouchMoving = false;
4703
4704			$( event.target ).parents( '.sp-thumbnail' ).one( 'click', function( event ) {
4705				event.preventDefault();
4706			});
4707
4708			// Remove the 'sp-swiping' class but with a delay
4709			// because there might be other event listeners that check
4710			// the existence of this class, and this class should still be 
4711			// applied for those listeners, since there was a swipe event
4712			setTimeout(function() {
4713				that.$thumbnailsContainer.removeClass( 'sp-swiping' );
4714			}, 1 );
4715
4716			// Keep the thumbnail scroller inside the bounds
4717			if ( this.thumbnailsPosition > 0 ) {
4718				this._moveThumbnailsTo( 0 );
4719			} else if ( this.thumbnailsPosition < this.thumbnailsContainerSize - this.thumbnailsSize ) {
4720				this._moveThumbnailsTo( this.thumbnailsContainerSize - this.thumbnailsSize );
4721			}
4722
4723			// Fire the 'thumbnailsMoveComplete' event
4724			this.trigger({ type: 'thumbnailsMoveComplete' });
4725			if ( $.isFunction( this.settings.thumbnailsMoveComplete ) ) {
4726				this.settings.thumbnailsMoveComplete.call( this, { type: 'thumbnailsMoveComplete' });
4727			}
4728		},
4729
4730		// Destroy the module
4731		destroyThumbnailTouchSwipe: function() {
4732			this.off( 'update.' + NS );
4733
4734			if ( this.isThumbnailScroller === false ) {
4735				return;
4736			}
4737
4738			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.startEvent + '.' + NS );
4739			this.$thumbnails.off( this.thumbnailTouchSwipeEvents.moveEvent + '.' + NS );
4740			this.$thumbnails.off( 'dragstart.' + NS );
4741			$( document ).off( this.thumbnailTouchSwipeEvents.endEvent + '.' + this.uniqueId + '.' + NS );
4742			this.$thumbnails.removeClass( 'sp-grab' );
4743		},
4744
4745		thumbnailTouchSwipeDefaults: {
4746
4747			// Indicates whether the touch swipe will be enabled for thumbnails
4748			thumbnailTouchSwipe: true
4749		}
4750	};
4751
4752	$.SliderPro.addModule( 'ThumbnailTouchSwipe', ThumbnailTouchSwipe );
4753
4754})( window, jQuery );
4755
4756// Thumbnail Arrows module for Slider Pro.
4757// 
4758// Adds thumbnail arrows for moving the thumbnail scroller.
4759;(function( window, $ ) {
4760
4761	"use strict";
4762	
4763	var NS = 'ThumbnailArrows.' + $.SliderPro.namespace;
4764
4765	var ThumbnailArrows = {
4766
4767		// Reference to the arrows container
4768		$thumbnailArrows: null,
4769
4770		// Reference to the 'previous' thumbnail arrow
4771		$previousThumbnailArrow: null,
4772
4773		// Reference to the 'next' thumbnail arrow
4774		$nextThumbnailArrow: null,
4775
4776		initThumbnailArrows: function() {
4777			var that = this;
4778
4779			this.on( 'update.' + NS, $.proxy( this._thumbnailArrowsOnUpdate, this ) );
4780			
4781			// Check if the arrows need to be visible or invisible when the thumbnail scroller
4782			// resizes and when the thumbnail scroller moves.
4783			this.on( 'sliderResize.' + NS + ' ' + 'thumbnailsMoveComplete.' + NS, function() {
4784				if ( that.isThumbnailScroller === true && that.settings.thumbnailArrows === true ) {
4785					that._checkThumbnailArrowsVisibility();
4786				}
4787			});
4788		},
4789		
4790		// Called when the slider is updated
4791		_thumbnailArrowsOnUpdate: function() {
4792			var that = this;
4793			
4794			if ( this.isThumbnailScroller === false ) {
4795				return;
4796			}
4797
4798			// Create or remove the thumbnail scroller arrows
4799			if ( this.settings.thumbnailArrows === true && this.$thumbnailArrows === null ) {
4800				this.$thumbnailArrows = $( '<div class="sp-thumbnail-arrows"></div>' ).appendTo( this.$thumbnailsContainer );
4801				
4802				this.$previousThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-previous-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows );
4803				this.$nextThumbnailArrow = $( '<div class="sp-thumbnail-arrow sp-next-thumbnail-arrow"></div>' ).appendTo( this.$thumbnailArrows );
4804
4805				this.$previousThumbnailArrow.on( 'click.' + NS, function() {
4806					var previousPosition = Math.min( 0, that.thumbnailsPosition + that.thumbnailsContainerSize );
4807					that._moveThumbnailsTo( previousPosition );
4808				});
4809
4810				this.$nextThumbnailArrow.on( 'click.' + NS, function() {
4811					var nextPosition = Math.max( that.thumbnailsContainerSize - that.thumbnailsSize, that.thumbnailsPosition - that.thumbnailsContainerSize );
4812					that._moveThumbnailsTo( nextPosition );
4813				});
4814			} else if ( this.settings.thumbnailArrows === false && this.$thumbnailArrows !== null ) {
4815				this._removeThumbnailArrows();
4816			}
4817
4818			// Add fading functionality and check if the arrows need to be visible or not
4819			if ( this.settings.thumbnailArrows === true ) {
4820				if ( this.settings.fadeThumbnailArrows === true ) {
4821					this.$thumbnailArrows.addClass( 'sp-fade-thumbnail-arrows' );
4822				} else if ( this.settings.fadeThumbnailArrows === false ) {
4823					this.$thumbnailArrows.removeClass( 'sp-fade-thumbnail-arrows' );
4824				}
4825
4826				this._checkThumbnailArrowsVisibility();
4827			}
4828		},
4829
4830		// Checks if the 'next' or 'previous' arrows need to be visible or hidden,
4831		// based on the position of the thumbnail scroller
4832		_checkThumbnailArrowsVisibility: function() {
4833			if ( this.thumbnailsPosition === 0 ) {
4834				this.$previousThumbnailArrow.css( 'display', 'none' );
4835			} else {
4836				this.$previousThumbnailArrow.css( 'display', 'block' );
4837			}
4838
4839			if ( this.thumbnailsPosition === this.thumbnailsContainerSize - this.thumbnailsSize ) {
4840				this.$nextThumbnailArrow.css( 'display', 'none' );
4841			} else {
4842				this.$nextThumbnailArrow.css( 'display', 'block' );
4843			}
4844		},
4845
4846		// Remove the thumbnail arrows
4847		_removeThumbnailArrows: function() {
4848			if ( this.$thumbnailArrows !== null ) {
4849				this.$previousThumbnailArrow.off( 'click.' + NS );
4850				this.$nextThumbnailArrow.off( 'click.' + NS );
4851				this.$thumbnailArrows.remove();
4852				this.$thumbnailArrows = null;
4853			}
4854		},
4855
4856		// Destroy the module
4857		destroyThumbnailArrows: function() {
4858			this._removeThumbnailArrows();
4859			this.off( 'update.' + NS );
4860			this.off( 'sliderResize.' + NS );
4861			this.off( 'thumbnailsMoveComplete.' + NS );
4862		},
4863
4864		thumbnailArrowsDefaults: {
4865
4866			// Indicates whether the thumbnail arrows will be enabled
4867			thumbnailArrows: false,
4868
4869			// Indicates whether the thumbnail arrows will be faded
4870			fadeThumbnailArrows: true
4871		}
4872	};
4873
4874	$.SliderPro.addModule( 'ThumbnailArrows', ThumbnailArrows );
4875
4876})( window, jQuery );
4877
4878// Video module for Slider Pro
4879//
4880// Adds automatic control for several video players and providers
4881;(function( window, $ ) {
4882
4883	"use strict";
4884
4885	var NS = 'Video.' + $.SliderPro.namespace;
4886	
4887	var Video = {
4888
4889		initVideo: function() {
4890			this.on( 'update.' + NS, $.proxy( this._videoOnUpdate, this ) );
4891			this.on( 'gotoSlideComplete.' + NS, $.proxy( this._videoOnGotoSlideComplete, this ) );
4892		},
4893
4894		_videoOnUpdate: function() {
4895			var that = this;
4896
4897			// Find all the inline videos and initialize them
4898			this.$slider.find( '.sp-video' ).not( 'a, [data-init]' ).each(function() {
4899				var video = $( this );
4900				that._initVideo( video );
4901			});
4902
4903			// Find all the lazy-loaded videos and preinitialize them. They will be initialized
4904			// only when their play button is clicked.
4905			this.$slider.find( 'a.sp-video' ).not( '[data-preinit]' ).each(function() {
4906				var video = $( this );
4907				that._preinitVideo( video );
4908			});
4909		},
4910
4911		// Initialize the target video
4912		_initVideo: function( video ) {
4913			var that = this;
4914
4915			video.attr( 'data-init', true )
4916				.videoController();
4917
4918			// When the video starts playing, pause the autoplay if it's running
4919			video.on( 'videoPlay.' + NS, function() {
4920				if ( that.settings.playVideoAction === 'stopAutoplay' && typeof that.stopAutoplay !== 'undefined' ) {
4921					that.stopAutoplay();
4922					that.settings.autoplay = false;
4923				}
4924
4925				// Fire the 'videoPlay' event
4926				var eventObject = { type: 'videoPlay', video: video };
4927				that.trigger( eventObject );
4928				if ( $.isFunction( that.settings.videoPlay ) ) {
4929					that.settings.videoPlay.call( that, eventObject );
4930				}
4931			});
4932
4933			// When the video is paused, restart the autoplay
4934			video.on( 'videoPause.' + NS, function() {
4935				if ( that.settings.pauseVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) {
4936					that.startAutoplay();
4937					that.settings.autoplay = true;
4938				}
4939
4940				// Fire the 'videoPause' event
4941				var eventObject = { type: 'videoPause', video: video };
4942				that.trigger( eventObject );
4943				if ( $.isFunction( that.settings.videoPause ) ) {
4944					that.settings.videoPause.call( that, eventObject );
4945				}
4946			});
4947
4948			// When the video ends, restart the autoplay (which was paused during the playback), or
4949			// go to the next slide, or replay the video
4950			video.on( 'videoEnded.' + NS, function() {
4951				if ( that.settings.endVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined' ) {
4952					that.startAutoplay();
4953					that.settings.autoplay = true;
4954				} else if ( that.settings.endVideoAction === 'nextSlide' ) {
4955					that.nextSlide();
4956				} else if ( that.settings.endVideoAction === 'replayVideo' ) {
4957					video.videoController( 'replay' );
4958				}
4959
4960				// Fire the 'videoEnd' event
4961				var eventObject = { type: 'videoEnd', video: video };
4962				that.trigger( eventObject );
4963				if ( $.isFunction(that.settings.videoEnd ) ) {
4964					that.settings.videoEnd.call( that, eventObject );
4965				}
4966			});
4967		},
4968
4969		// Pre-initialize the video. This is for lazy loaded videos.
4970		_preinitVideo: function( video ) {
4971			var that = this;
4972
4973			video.attr( 'data-preinit', true );
4974
4975			// When the video poster is clicked, remove the poster and create
4976			// the inline video
4977			video.on( 'click.' + NS, function( event ) {
4978
4979				// If the video is being dragged, don't start the video
4980				if ( that.$slider.hasClass( 'sp-swiping' ) ) {
4981					return;
4982				}
4983
4984				event.preventDefault();
4985
4986				var href = video.attr( 'href' ),
4987					iframe,
4988					provider,
4989					regExp,
4990					match,
4991					id,
4992					src,
4993					videoAttributes,
4994					videoWidth = video.children( 'img' ).attr( 'width' ),
4995					videoHeight = video.children( 'img' ).attr( 'height');
4996
4997				// Check if it's a youtube or vimeo video
4998				if ( href.indexOf( 'youtube' ) !== -1 || href.indexOf( 'youtu.be' ) !== -1 ) {
4999					provider = 'youtube';
5000				} else if ( href.indexOf( 'vimeo' ) !== -1 ) {
5001					provider = 'vimeo';
5002				}
5003
5004				// Get the id of the video
5005				regExp = provider === 'youtube' ? /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ : /http:\/\/(www\.)?vimeo.com\/(\d+)/;
5006				match = href.match( regExp );
5007				id = match[2];
5008
5009				// Get the source of the iframe that will be created
5010				src = provider === 'youtube' ? 'http://www.youtube.com/embed/' + id + '?enablejsapi=1&wmode=opaque' : 'http://player.vimeo.com/video/'+ id +'?api=1';
5011				
5012				// Get the attributes passed to the video link and then pass them to the iframe's src
5013				videoAttributes = href.split( '?' )[ 1 ];
5014
5015				if ( typeof videoAttributes !== 'undefined' ) {
5016					videoAttributes = videoAttributes.split( '&' );
5017
5018					$.each( videoAttributes, function( index, value ) {
5019						if ( value.indexOf( id ) === -1 ) {
5020							src += '&' + value;
5021						}
5022					});
5023				}
5024
5025				// Create the iframe
5026				iframe = $( '<iframe></iframe>' )
5027					.attr({
5028						'src': src,
5029						'width': videoWidth,
5030						'height': videoHeight,
5031						'class': video.attr( 'class' ),
5032						'frameborder': 0
5033					}).insertBefore( video );
5034
5035				// Initialize the video and play it
5036				that._initVideo( iframe );
5037				iframe.videoController( 'play' );
5038
5039				// Hide the video poster
5040				video.css( 'display', 'none' );
5041			});
5042		},
5043
5044		// Called when a new slide is selected
5045		_videoOnGotoSlideComplete: function( event ) {
5046
5047			// Get the video from the previous slide
5048			var previousVideo = this.$slides.find( '.sp-slide' ).eq( event.previousIndex ).find( '.sp-video[data-init]' );
5049
5050			// Handle the video from the previous slide by stopping it, or pausing it,
5051			// or remove it, depending on the value of the 'leaveVideoAction' option.
5052			if ( event.previousIndex !== -1 && previousVideo.length !== 0 ) {
5053				if ( this.settings.leaveVideoAction === 'stopVideo' ) {
5054					previousVideo.videoController( 'stop' );
5055				} else if ( this.settings.leaveVideoAction === 'pauseVideo' ) {
5056					previousVideo.videoController( 'pause' );
5057				} else if ( this.settings.leaveVideoAction === 'removeVideo'  ) {
5058					// If the video was lazy-loaded, remove it and show the poster again. If the video
5059					// was not lazy-loaded, but inline, stop the video.
5060					if ( previousVideo.siblings( 'a.sp-video' ).length !== 0 ) {
5061						previousVideo.siblings( 'a.sp-video' ).css( 'display', '' );
5062						previousVideo.videoController( 'destroy' );
5063						previousVideo.remove();
5064					} else {
5065						previousVideo.videoController( 'stop' );
5066					}
5067				}
5068			}
5069
5070			// Handle the video from the selected slide
5071			if ( this.settings.reachVideoAction === 'playVideo' ) {
5072				var loadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-init]' ),
5073					unloadedVideo = this.$slides.find( '.sp-slide' ).eq( event.index ).find( '.sp-video[data-preinit]' );
5074
5075				// If the video was already initialized, play it. If it's not initialized (because
5076				// it's lazy loaded) initialize it and play it.
5077				if ( loadedVideo.length !== 0 ) {
5078					loadedVideo.videoController( 'play' );
5079				} else if ( unloadedVideo.length !== 0 ) {
5080					unloadedVideo.trigger( 'click.' + NS );
5081				}
5082			}
5083		},
5084
5085		// Destroy the module
5086		destroyVideo: function() {
5087			this.$slider.find( '.sp-video[ data-preinit ]' ).each(function() {
5088				var video = $( this );
5089				video.removeAttr( 'data-preinit' );
5090				video.off( 'click.' + NS );
5091			});
5092
5093			// Loop through the all the videos and destroy them
5094			this.$slider.find( '.sp-video[ data-init ]' ).each(function() {
5095				var video = $( this );
5096				video.removeAttr( 'data-init' );
5097				video.off( 'Video' );
5098				video.videoController( 'destroy' );
5099			});
5100
5101			this.off( 'update.' + NS );
5102			this.off( 'gotoSlideComplete.' + NS );
5103		},
5104
5105		videoDefaults: {
5106
5107			// Sets the action that the video will perform when its slide container is selected
5108			// ( 'playVideo' and 'none' )
5109			reachVideoAction: 'none',
5110
5111			// Sets the action that the video will perform when another slide is selected
5112			// ( 'stopVideo', 'pauseVideo', 'removeVideo' and 'none' )
5113			leaveVideoAction: 'pauseVideo',
5114
5115			// Sets the action that the slider will perform when the video starts playing
5116			// ( 'stopAutoplay' and 'none' )
5117			playVideoAction: 'stopAutoplay',
5118
5119			// Sets the action that the slider will perform when the video is paused
5120			// ( 'startAutoplay' and 'none' )
5121			pauseVideoAction: 'none',
5122
5123			// Sets the action that the slider will perform when the video ends
5124			// ( 'startAutoplay', 'nextSlide', 'replayVideo' and 'none' )
5125			endVideoAction: 'none',
5126
5127			// Called when the video starts playing
5128			videoPlay: function() {},
5129
5130			// Called when the video is paused
5131			videoPause: function() {},
5132
5133			// Called when the video ends
5134			videoEnd: function() {}
5135		}
5136	};
5137
5138	$.SliderPro.addModule( 'Video', Video );
5139	
5140})( window, jQuery );
5141
5142// Video Controller jQuery plugin
5143// Creates a universal controller for multiple video types and providers
5144;(function( $ ) {
5145
5146	"use strict";
5147
5148// Check if an iOS device is used.
5149// This information is important because a video can not be
5150// controlled programmatically unless the user has started the video manually.
5151var	isIOS = window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ? true : false;
5152
5153var VideoController = function( instance, options ) {
5154	this.$video = $( instance );
5155	this.options = options;
5156	this.settings = {};
5157	this.player = null;
5158
5159	this._init();
5160};
5161
5162VideoController.prototype = {
5163
5164	_init: function() {
5165		this.settings = $.extend( {}, this.defaults, this.options );
5166
5167		var that = this,
5168			players = $.VideoController.players,
5169			videoID = this.$video.attr( 'id' );
5170
5171		// Loop through the available video players
5172		// and check if the targeted video element is supported by one of the players.
5173		// If a compatible type is found, store the video type.
5174		for ( var name in players ) {
5175			if ( typeof players[ name ] !== 'undefined' && players[ name ].isType( this.$video ) ) {
5176				this.player = new players[ name ]( this.$video );
5177				break;
5178			}
5179		}
5180
5181		// Return if the player could not be instantiated
5182		if ( this.player === null ) {
5183			return;
5184		}
5185
5186		// Add event listeners
5187		var events = [ 'ready', 'start', 'play', 'pause', 'ended' ];
5188		
5189		$.each( events, function( index, element ) {
5190			var event = 'video' + element.charAt( 0 ).toUpperCase() + element.slice( 1 );
5191
5192			that.player.on( element, function() {
5193				that.trigger({ type: event, video: videoID });
5194				if ( $.isFunction( that.settings[ event ] ) ) {
5195					that.settings[ event ].call( that, { type: event, video: videoID } );
5196				}
5197			});
5198		});
5199	},
5200	
5201	play: function() {
5202		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'playing' ) {
5203			return;
5204		}
5205
5206		this.player.play();
5207	},
5208	
5209	stop: function() {
5210		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'stopped' ) {
5211			return;
5212		}
5213
5214		this.player.stop();
5215	},
5216	
5217	pause: function() {
5218		if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'paused' ) {
5219			return;
5220		}
5221
5222		this.player.pause();
5223	},
5224
5225	replay: function() {
5226		if ( isIOS === true && this.player.isStarted() === false ) {
5227			return;
5228		}
5229		
5230		this.player.replay();
5231	},
5232
5233	on: function( type, callback ) {
5234		return this.$video.on( type, callback );
5235	},
5236	
5237	off: function( type ) {
5238		return this.$video.off( type );
5239	},
5240
5241	trigger: function( data ) {
5242		return this.$video.triggerHandler( data );
5243	},
5244
5245	destroy: function() {
5246		if ( this.player.isStarted() === true ) {
5247			this.stop();
5248		}
5249
5250		this.player.off( 'ready' );
5251		this.player.off( 'start' );
5252		this.player.off( 'play' );
5253		this.player.off( 'pause' );
5254		this.player.off( 'ended' );
5255
5256		this.$video.removeData( 'videoController' );
5257	},
5258
5259	defaults: {
5260		videoReady: function() {},
5261		videoStart: function() {},
5262		videoPlay: function() {},
5263		videoPause: function() {},
5264		videoEnded: function() {}
5265	}
5266};
5267
5268$.VideoController = {
5269	players: {},
5270
5271	addPlayer: function( name, player ) {
5272		this.players[ name ] = player;
5273	}
5274};
5275
5276$.fn.videoController = function( options ) {
5277	var args = Array.prototype.slice.call( arguments, 1 );
5278
5279	return this.each(function() {
5280		// Instantiate the video controller or call a function on the current instance
5281		if ( typeof $( this ).data( 'videoController' ) === 'undefined' ) {
5282			var newInstance = new VideoController( this, options );
5283
5284			// Store a reference to the instance created
5285			$( this ).data( 'videoController', newInstance );
5286		} else if ( typeof options !== 'undefined' ) {
5287			var	currentInstance = $( this ).data( 'videoController' );
5288
5289			// Check the type of argument passed
5290			if ( typeof currentInstance[ options ] === 'function' ) {
5291				currentInstance[ options ].apply( currentInstance, args );
5292			} else {
5293				$.error( options + ' does not exist in videoController.' );
5294			}
5295		}
5296	});
5297};
5298
5299// Base object for the video players
5300var Video = function( video ) {
5301	this.$video = video;
5302	this.player = null;
5303	this.ready = false;
5304	this.started = false;
5305	this.state = '';
5306	this.events = $({});
5307
5308	this._init();
5309};
5310
5311Video.prototype = {
5312	_init: function() {},
5313
5314	play: function() {},
5315
5316	pause: function() {},
5317
5318	stop: function() {},
5319
5320	replay: function() {},
5321
5322	isType: function() {},
5323
5324	isReady: function() {
5325		return this.ready;
5326	},
5327
5328	isStarted: function() {
5329		return this.started;
5330	},
5331
5332	getState: function() {
5333		return this.state;
5334	},
5335
5336	on: function( type, callback ) {
5337		return this.events.on( type, callback );
5338	},
5339	
5340	off: function( type ) {
5341		return this.events.off( type );
5342	},
5343
5344	trigger: function( data ) {
5345		return this.events.triggerHandler( data );
5346	}
5347};
5348
5349// YouTube video
5350var YoutubeVideoHelper = {
5351	youtubeAPIAdded: false,
5352	youtubeVideos: []
5353};
5354
5355var YoutubeVideo = function( video ) {
5356	this.init = false;
5357	var youtubeAPILoaded = window.YT && window.YT.Player;
5358
5359	if ( typeof youtubeAPILoaded !== 'undefined' ) {
5360		Video.call( this, video );
5361	} else {
5362		YoutubeVideoHelper.youtubeVideos.push({ 'video': video, 'scope': this });
5363		
5364		if ( YoutubeVideoHelper.youtubeAPIAdded === false ) {
5365			YoutubeVideoHelper.youtubeAPIAdded = true;
5366
5367			var tag = document.createElement( 'script' );
5368			tag.src = "http://www.youtube.com/player_api";
5369			var firstScriptTag = document.getElementsByTagName( 'script' )[0];
5370			firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
5371
5372			window.onYouTubePlayerAPIReady = function() {
5373				$.each( YoutubeVideoHelper.youtubeVideos, function( index, element ) {
5374					Video.call( element.scope, element.video );
5375				});
5376			};
5377		}
5378	}
5379};
5380
5381YoutubeVideo.prototype = new Video();
5382YoutubeVideo.prototype.constructor = YoutubeVideo;
5383$.VideoController.addPlayer( 'YoutubeVideo', YoutubeVideo );
5384
5385YoutubeVideo.isType = function( video ) {
5386	if ( video.is( 'iframe' ) ) {
5387		var src = video.attr( 'src' );
5388
5389		if ( src.indexOf( 'youtube.com' ) !== -1 || src.indexOf( 'youtu.be' ) !== -1 ) {
5390			return true;
5391		}
5392	}
5393
5394	return false;
5395};
5396
5397YoutubeVideo.prototype._init = function() {
5398	this.init = true;
5399	this._setup();
5400};
5401	
5402YoutubeVideo.prototype._setup = function() {
5403	var that = this;
5404
5405	// Get a reference to the player
5406	this.player = new YT.Player( this.$video[0], {
5407		events: {
5408			'onReady': function() {
5409				that.trigger({ type: 'ready' });
5410				that.ready = true;
5411			},
5412			
5413			'onStateChange': function( event ) {
5414				switch ( event.data ) {
5415					case YT.PlayerState.PLAYING:
5416						if (that.started === false) {
5417							that.started = true;
5418							that.trigger({ type: 'start' });
5419						}
5420
5421						that.state = 'playing';
5422						that.trigger({ type: 'play' });
5423						break;
5424					
5425					case YT.PlayerState.PAUSED:
5426						that.state = 'paused';
5427						that.trigger({ type: 'pause' });
5428						break;
5429					
5430					case YT.PlayerState.ENDED:
5431						that.state = 'ended';
5432						that.trigger({ type: 'ended' });
5433						break;
5434				}
5435			}
5436		}
5437	});
5438};
5439
5440YoutubeVideo.prototype.play = function() {
5441	var that = this;
5442
5443	if ( this.ready === true ) {
5444		this.player.playVideo();
5445	} else {
5446		var timer = setInterval(function() {
5447			if ( that.ready === true ) {
5448				clearInterval( timer );
5449				that.player.playVideo();
5450			}
5451		}, 100 );
5452	}
5453};
5454
5455YoutubeVideo.prototype.pause = function() {
5456	// On iOS, simply pausing the video can make other videos unresponsive
5457	// so we stop the video instead.
5458	if ( isIOS === true ) {
5459		this.stop();
5460	} else {
5461		this.player.pauseVideo();
5462	}
5463};
5464
5465YoutubeVideo.prototype.stop = function() {
5466	this.player.seekTo( 1 );
5467	this.player.stopVideo();
5468	this.state = 'stopped';
5469};
5470
5471YoutubeVideo.prototype.replay = function() {
5472	this.player.seekTo( 1 );
5473	this.player.playVideo();
5474};
5475
5476YoutubeVideo.prototype.on = function( type, callback ) {
5477	var that = this;
5478
5479	if ( this.init === true ) {
5480		Video.prototype.on.call( this, type, callback );
5481	} else {
5482		var timer = setInterval(function() {
5483			if ( that.init === true ) {
5484				clearInterval( timer );
5485				Video.prototype.on.call( that, type, callback );
5486			}
5487		}, 100 );
5488	}
5489};
5490
5491// Vimeo video
5492var VimeoVideoHelper = {
5493	vimeoAPIAdded: false,
5494	vimeoVideos: []
5495};
5496
5497var VimeoVideo = function( video ) {
5498	this.init = false;
5499
5500	if ( typeof window.Froogaloop !== 'undefined' ) {
5501		Video.call( this, video );
5502	} else {
5503		VimeoVideoHelper.vimeoVideos.push({ 'video': video, 'scope': this });
5504
5505		if ( VimeoVideoHelper.vimeoAPIAdded === false ) {
5506			VimeoVideoHelper.vimeoAPIAdded = true;
5507
5508			var tag = document.createElement('script');
5509			tag.src = "http://a.vimeocdn.com/js/froogaloop2.min.js";
5510			var firstScriptTag = document.getElementsByTagName( 'script' )[0];
5511			firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
5512		
5513			var checkVimeoAPITimer = setInterval(function() {
5514				if ( typeof window.Froogaloop !== 'undefined' ) {
5515					clearInterval( checkVimeoAPITimer );
5516					
5517					$.each( VimeoVideoHelper.vimeoVideos, function( index, element ) {
5518						Video.call( element.scope, element.video );
5519					});
5520				}
5521			}, 100 );
5522		}
5523	}
5524};
5525
5526VimeoVideo.prototype = new Video();
5527VimeoVideo.prototype.constructor = VimeoVideo;
5528$.VideoController.addPlayer( 'VimeoVideo', VimeoVideo );
5529
5530VimeoVideo.isType = function( video ) {
5531	if ( video.is( 'iframe' ) ) {
5532		var src = video.attr('src');
5533
5534		if ( src.indexOf( 'vimeo.com' ) !== -1 ) {
5535			return true;
5536		}
5537	}
5538
5539	return false;
5540};
5541
5542VimeoVideo.prototype._init = function() {
5543	this.init = true;
5544	this._setup();
5545};
5546
5547VimeoVideo.prototype._setup = function() {
5548	var that = this;
5549
5550	// Get a reference to the player
5551	this.player = $f( this.$video[0] );
5552	
5553	this.player.addEvent( 'ready', function() {
5554		that.ready = true;
5555		that.trigger({ type: 'ready' });
5556		
5557		that.player.addEvent( 'play', function() {
5558			if ( that.started === false ) {
5559				that.started = true;
5560				that.trigger({ type: 'start' });
5561			}
5562
5563			that.state = 'playing';
5564			that.trigger({ type: 'play' });
5565		});
5566		
5567		that.player.addEvent( 'pause', function() {
5568			that.state = 'paused';
5569			that.trigger({ type: 'pause' });
5570		});
5571		
5572		that.player.addEvent( 'finish', function() {
5573			that.state = 'ended';
5574			that.trigger({ type: 'ended' });
5575		});
5576	});
5577};
5578
5579VimeoVideo.prototype.play = function() {
5580	var that = this;
5581
5582	if ( this.ready === true ) {
5583		this.player.api( 'play' );
5584	} else {
5585		var timer = setInterval(function() {
5586			if ( that.ready === true ) {
5587				clearInterval( timer );
5588				that.player.api( 'play' );
5589			}
5590		}, 100 );
5591	}
5592};
5593
5594VimeoVideo.prototype.pause = function() {
5595	this.player.api( 'pause' );
5596};
5597
5598VimeoVideo.prototype.stop = function() {
5599	this.player.api( 'seekTo', 0 );
5600	this.player.api( 'pause' );
5601	this.state = 'stopped';
5602};
5603
5604VimeoVideo.prototype.replay = function() {
5605	this.player.api( 'seekTo', 0 );
5606	this.player.api( 'play' );
5607};
5608
5609VimeoVideo.prototype.on = function( type, callback ) {
5610	var that = this;
5611
5612	if ( this.init === true ) {
5613		Video.prototype.on.call( this, type, callback );
5614	} else {
5615		var timer = setInterval(function() {
5616			if ( that.init === true ) {
5617				clearInterval( timer );
5618				Video.prototype.on.call( that, type, callback );
5619			}
5620		}, 100 );
5621	}
5622};
5623
5624// HTML5 video
5625var HTML5Video = function( video ) {
5626	Video.call( this, video );
5627};
5628
5629HTML5Video.prototype = new Video();
5630HTML5Video.prototype.constructor = HTML5Video;
5631$.VideoController.addPlayer( 'HTML5Video', HTML5Video );
5632
5633HTML5Video.isType = function( video ) {
5634	if ( video.is( 'video' ) && video.hasClass( 'video-js' ) === false && video.hasClass( 'sublime' ) === false ) {
5635		return true;
5636	}
5637
5638	return false;
5639};
5640
5641HTML5Video.prototype._init = function() {
5642	var that = this;
5643
5644	// Get a reference to the player
5645	this.player = this.$video[0];
5646	this.ready = true;
5647
5648	this.player.addEventListener( 'play', function() {
5649		if ( that.started === false ) {
5650			that.started = true;
5651			that.trigger({ type: 'start' });
5652		}
5653
5654		that.state = 'playing';
5655		that.trigger({ type: 'play' });
5656	});
5657	
5658	this.player.addEventListener( 'pause', function() {
5659		that.state = 'paused';
5660		that.trigger({ type: 'pause' });
5661	});
5662	
5663	this.player.addEventListener( 'ended', function() {
5664		that.state = 'ended';
5665		that.trigger({ type: 'ended' });
5666	});
5667};
5668
5669HTML5Video.prototype.play = function() {
5670	this.player.play();
5671};
5672
5673HTML5Video.prototype.pause = function() {
5674	this.player.pause();
5675};
5676
5677HTML5Video.prototype.stop = function() {
5678	this.player.currentTime = 0;
5679	this.player.pause();
5680	this.state = 'stopped';
5681};
5682
5683HTML5Video.prototype.replay = function() {
5684	this.player.currentTime = 0;
5685	this.player.play();
5686};
5687
5688// VideoJS video
5689var VideoJSVideo = function( video ) {
5690	Video.call( this, video );
5691};
5692
5693VideoJSVideo.prototype = new Video();
5694VideoJSVideo.prototype.constructor = VideoJSVideo;
5695$.VideoController.addPlayer( 'VideoJSVideo', VideoJSVideo );
5696
5697VideoJSVideo.isType = function( video ) {
5698	if ( ( typeof video.attr( 'data-videojs-id' ) !== 'undefined' || video.hasClass( 'video-js' ) ) && typeof videojs !== 'undefined' ) {
5699		return true;
5700	}
5701
5702	return false;
5703};
5704
5705VideoJSVideo.prototype._init = function() {
5706	var that = this,
5707		videoID = this.$video.hasClass( 'video-js' ) ? this.$video.attr( 'id' ) : this.$video.attr( 'data-videojs-id' );
5708	
5709	this.player = videojs( videoID );
5710
5711	this.player.ready(function() {
5712		that.ready = true;
5713		that.trigger({ type: 'ready' });
5714
5715		that.player.on( 'play', function() {
5716			if ( that.started === false ) {
5717				that.started = true;
5718				that.trigger({ type: 'start' });
5719			}
5720
5721			that.state = 'playing';
5722			that.trigger({ type: 'play' });
5723		});
5724		
5725		that.player.on( 'pause', function() {
5726			that.state = 'paused';
5727			that.trigger({ type: 'pause' });
5728		});
5729		
5730		that.player.on( 'ended', function() {
5731			that.state = 'ended';
5732			that.trigger({ type: 'ended' });
5733		});
5734	});
5735};
5736
5737VideoJSVideo.prototype.play = function() {
5738	this.player.play();
5739};
5740
5741VideoJSVideo.prototype.pause = function() {
5742	this.player.pause();
5743};
5744
5745VideoJSVideo.prototype.stop = function() {
5746	this.player.currentTime( 0 );
5747	this.player.pause();
5748	this.state = 'stopped';
5749};
5750
5751VideoJSVideo.prototype.replay = function() {
5752	this.player.currentTime( 0 );
5753	this.player.play();
5754};
5755
5756// Sublime video
5757var SublimeVideo = function( video ) {
5758	Video.call( this, video );
5759};
5760
5761SublimeVideo.prototype = new Video();
5762SublimeVideo.prototype.constructor = SublimeVideo;
5763$.VideoController.addPlayer( 'SublimeVideo', SublimeVideo );
5764
5765SublimeVideo.isType = function( video ) {
5766	if ( video.hasClass( 'sublime' ) && typeof sublime !== 'undefined' ) {
5767		return true;
5768	}
5769
5770	return false;
5771};
5772
5773SublimeVideo.prototype._init = function() {
5774	var that = this;
5775
5776	sublime.ready(function() {
5777		// Get a reference to the player
5778		that.player = sublime.player( that.$video.attr( 'id' ) );
5779
5780		that.ready = true;
5781		that.trigger({ type: 'ready' });
5782
5783		that.player.on( 'play', function() {
5784			if ( that.started === false ) {
5785				that.started = true;
5786				that.trigger({ type: 'start' });
5787			}
5788
5789			that.state = 'playing';
5790			that.trigger({ type: 'play' });
5791		});
5792
5793		that.player.on( 'pause', function() {
5794			that.state = 'paused';
5795			that.trigger({ type: 'pause' });
5796		});
5797
5798		that.player.on( 'stop', function() {
5799			that.state = 'stopped';
5800			that.trigger({ type: 'stop' });
5801		});
5802
5803		that.player.on( 'end', function() {
5804			that.state = 'ended';
5805			that.trigger({ type: 'ended' });
5806		});
5807	});
5808};
5809
5810SublimeVideo.prototype.play = function() {
5811	this.player.play();
5812};
5813
5814SublimeVideo.prototype.pause = function() {
5815	this.player.pause();
5816};
5817
5818SublimeVideo.prototype.stop = function() {
5819	this.player.stop();
5820};
5821
5822SublimeVideo.prototype.replay = function() {
5823	this.player.stop();
5824	this.player.play();
5825};
5826
5827// JWPlayer video
5828var JWPlayerVideo = function( video ) {
5829	Video.call( this, video );
5830};
5831
5832JWPlayerVideo.prototype = new Video();
5833JWPlayerVideo.prototype.constructor = JWPlayerVideo;
5834$.VideoController.addPlayer( 'JWPlayerVideo', JWPlayerVideo );
5835
5836JWPlayerVideo.isType = function( video ) {
5837	if ( ( typeof video.attr( 'data-jwplayer-id' ) !== 'undefined' || video.hasClass( 'jwplayer' ) || video.find( "object[data*='jwplayer']" ).length !== 0 ) &&
5838		typeof jwplayer !== 'undefined') {
5839		return true;
5840	}
5841
5842	return false;
5843};
5844
5845JWPlayerVideo.prototype._init = function() {
5846	var that = this,
5847		videoID;
5848
5849	if ( this.$video.hasClass( 'jwplayer' ) ) {
5850		videoID = this.$video.attr( 'id' );
5851	} else if ( typeof this.$video.attr( 'data-jwplayer-id' ) !== 'undefined' ) {
5852		videoID = this.$video.attr( 'data-jwplayer-id');
5853	} else if ( this.$video.find( "object[data*='jwplayer']" ).length !== 0 ) {
5854		videoID = this.$video.find( 'object' ).attr( 'id' );
5855	}
5856
5857	// Get a reference to the player
5858	this.player = jwplayer( videoID );
5859
5860	this.player.onReady(function() {
5861		that.ready = true;
5862		that.trigger({ type: 'ready' });
5863	
5864		that.player.onPlay(function() {
5865			if ( that.started === false ) {
5866				that.started = true;
5867				that.trigger({ type: 'start' });
5868			}
5869
5870			that.state = 'playing';
5871			that.trigger({ type: 'play' });
5872		});
5873
5874		that.player.onPause(function() {
5875			that.state = 'paused';
5876			that.trigger({ type: 'pause' });
5877		});
5878		
5879		that.player.onComplete(function() {
5880			that.state = 'ended';
5881			that.trigger({ type: 'ended' });
5882		});
5883	});
5884};
5885
5886JWPlayerVideo.prototype.play = function() {
5887	this.player.play( true );
5888};
5889
5890JWPlayerVideo.prototype.pause = function() {
5891	this.player.pause( true );
5892};
5893
5894JWPlayerVideo.prototype.stop = function() {
5895	this.player.stop();
5896	this.state = 'stopped';
5897};
5898
5899JWPlayerVideo.prototype.replay = function() {
5900	this.player.seek( 0 );
5901	this.player.play( true );
5902};
5903
5904})( jQuery );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.