Advertisement
Guest User

Untitled

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