text: /*
   * Backstretch
   * http://srobbin.com/jquery-plugins/backstretch/
   *
   * Copyright (c) 2013 Scott Robbin
   * Licensed under the MIT license.
   */
  
  ;(function ($, window, undefined) {
    'use strict';
  
    /** @const */
    var YOUTUBE_REGEXP = /^.*(youtu\.be\/|youtube\.com\/v\/|youtube\.com\/embed\/|youtube\.com\/watch\?v=|youtube\.com\/watch\?.*\&v=)([^#\&\?]*).*/i;
    
    /* PLUGIN DEFINITION
     * ========================= */
  
    $.fn.backstretch = function (images, options) {
      var args = arguments;
  
      /*
       * Scroll the page one pixel to get the right window height on iOS
       * Pretty harmless for everyone else
      */
      if ($(window).scrollTop() === 0 ) {
        window.scrollTo(0, 0);
      }
  
      var returnValues;
      
      this.each(function (eachIndex) {
        var $this = $(this)
          , obj = $this.data('backstretch');
  
        // Do we already have an instance attached to this element?
        if (obj) {
  
          // Is this a method they're trying to execute?
          if (typeof args[0] === 'string' &&
              typeof obj[args[0]] === 'function') {
                
            // Call the method
            var returnValue = obj[args[0]].apply(obj, Array.prototype.slice.call(args, 1));
            if (returnValue === obj) { // If a method is chaining
              returnValue = undefined;
            }
            if (returnValue !== undefined) {
              returnValues = returnValues || [];
              returnValues[eachIndex] = returnValue;
            }
            
            return; // Nothing further to do
          }
  
          // Merge the old options with the new
          options = $.extend(obj.options, options);
  
          // Remove the old instance
          if ( obj.hasOwnProperty('destroy') ) {
            obj.destroy(true);
          }
        }
  
        // We need at least one image
        if (!images || (images && images.length === 0)) {
          var cssBackgroundImage = $this.css('background-image');
          if (cssBackgroundImage && cssBackgroundImage !== 'none') {
            images = [ { url: $this.css('backgroundImage').replace(/url\(|\)|"|'/g,"") } ];
          } else {
            $.error('No images were supplied for Backstretch, or element must have a CSS-defined background image.');
          }
        }
  
        obj = new Backstretch(this, images, options || {});
        $this.data('backstretch', obj);
      });
      
      return returnValues ? returnValues.length === 1 ? returnValues[0] : returnValues : this;
    };
  
    // If no element is supplied, we'll attach to body
    $.backstretch = function (images, options) {
      // Return the instance
      return $('body')
              .backstretch(images, options)
              .data('backstretch');
    };
  
    // Custom selector
    $.expr[':'].backstretch = function(elem) {
      return $(elem).data('backstretch') !== undefined;
    };
  
    /* DEFAULTS
     * ========================= */
  
    $.fn.backstretch.defaults = {
      duration: 5000                // Amount of time in between slides (if slideshow)
      , transition: 'fade'          // Type of transition between slides
      , transitionDuration: 0       // Duration of transition between slides
      , animateFirst: true          // Animate the transition of first image of slideshow in?
      , alignX: 0.5                 // The x-alignment for the image, can be 'left'|'center'|'right' or any number between 0.0 and 1.0
      , alignY: 0.5                 // The y-alignment for the image, can be 'top'|'center'|'bottom' or any number between 0.0 and 1.0
      , paused: false               // Whether the images should slide after given duration
      , start: 0                    // Index of the first image to show
      , preload: 2                  // How many images preload at a time?
      , preloadSize: 1              // How many images can we preload in parallel?
      , resolutionRefreshRate: 2500 // How long to wait before switching resolution?
      , resolutionChangeRatioThreshold: 0.1 // How much a change should it be before switching resolution?
    };
  
    /* STYLES
     *
     * Baked-in styles that we'll apply to our elements.
     * In an effort to keep the plugin simple, these are not exposed as options.
     * That said, anyone can override these in their own stylesheet.
     * ========================= */
    var styles = {
      wrap: {
        left: 0
        , top: 0
        , overflow: 'hidden'
        , margin: 0
        , padding: 0
        , height: '100%'
        , width: '100%'
        , zIndex: -999999
      }
      , itemWrapper: {
        position: 'absolute'
        , display: 'none'
        , margin: 0
        , padding: 0
        , border: 'none'
        , width: '100%'
        , height: '100%'
        , zIndex: -999999
      }
      , item: {
        position: 'absolute'
        , margin: 0
        , padding: 0
        , border: 'none'
        , width: '100%'
        , height: '100%'
        , maxWidth: 'none'
      }
    };
  
    /* Given an array of different options for an image,
     * choose the optimal image for the container size.
     *
     * Given an image template (a string with {{ width }} and/or
     * {{height}} inside) and a container object, returns the
     * image url with the exact values for the size of that
     * container.
     *
     * Returns an array of urls optimized for the specified resolution.
     *
     */
    var optimalSizeImages = (function () {
  
      /* Sorts the array of image sizes based on width */
      var widthInsertSort = function (arr) {
        for (var i = 1; i < arr.length; i++) {
          var tmp = arr[i],
              j = i;
          while (arr[j - 1] && parseInt(arr[j - 1].width, 10) > parseInt(tmp.width, 10)) {
            arr[j] = arr[j - 1];
            --j;
          }
          arr[j] = tmp;
        }
  
        return arr;
      };
  
      /* Given an array of various sizes of the same image and a container width,
       * return the best image.
       */
      var selectBest = function (containerWidth, containerHeight, imageSizes) {
  
        var devicePixelRatio = window.devicePixelRatio || 1;
        var deviceOrientation = getDeviceOrientation();
        var windowOrientation = getWindowOrientation();
        var wrapperOrientation = (containerHeight > containerWidth) ?
          'portrait' :
          (containerWidth > containerHeight ? 'landscape' : 'square');
  
        var lastAllowedImage = 0;
        var testWidth;
  
        for (var j = 0, image; j < imageSizes.length; j++) {
  
            image = imageSizes[j];
  
            // In case a new image was pushed in, process it:
            if (typeof image === 'string') {
                image = imageSizes[j] = { url: image };
            }
  
            if (image.pixelRatio && image.pixelRatio !== 'auto' && parseFloat(image.pixelRatio) !== devicePixelRatio) {
                // We disallowed choosing this image for current device pixel ratio,
                // So skip this one.
                continue;
            }
  
            if (image.deviceOrientation && image.deviceOrientation !== deviceOrientation) {
                // We disallowed choosing this image for current device orientation,
                // So skip this one.
                continue;
            }
  
            if (image.windowOrientation && image.windowOrientation !== deviceOrientation) {
                // We disallowed choosing this image for current window orientation,
                // So skip this one.
                continue;
            }
  
            if (image.orientation && image.orientation !== wrapperOrientation) {
                // We disallowed choosing this image for current element's orientation,
                // So skip this one.
                continue;
            }
  
            // Mark this one as the last one we investigated
            // which does not violate device pixel ratio rules.
            // We may choose this one later if there's no match.
            lastAllowedImage = j;
  
            // For most images, we match the specified width against element width,
            // And enforcing a limit depending on the "pixelRatio" property if specified.
            // But if a pixelRatio="auto", then we consider the width as the physical width of the image,
            // And match it while considering the device's pixel ratio.
            testWidth = containerWidth;
            if (image.pixelRatio === 'auto') {
                containerWidth *= devicePixelRatio;
            }
  
            // Stop when the width of the image is larger or equal to the container width
            if (image.width >= testWidth) {
                break;
            }
        }
  
        // Use the image located at where we stopped
        return imageSizes[Math.min(j, lastAllowedImage)];
      };
      
      var replaceTagsInUrl = function (url, templateReplacer) {
          
          if (typeof url === 'string') {
              url = url.replace(/{{(width|height)}}/g, templateReplacer);
          } else if (url instanceof Array) {
              for (var i = 0; i < url.length; i++) {
                  if (url[i].src) {
                      url[i].src = replaceTagsInUrl(url[i].src, templateReplacer);
                  } else {
                      url[i] = replaceTagsInUrl(url[i], templateReplacer);
                  }
              }
          }
          
          return url;
      };
  
      return function ($container, images) {
        var containerWidth = $container.width(),
            containerHeight = $container.height();
  
        var chosenImages = [];
  
        var templateReplacer = function (match, key) {
          if (key === 'width') {
            return containerWidth;
          }
          if (key === 'height') {
            return containerHeight;
          }
          return match;
        };
  
        for (var i = 0; i < images.length; i++) {
          if ($.isArray(images[i])) {
            images[i] = widthInsertSort(images[i]);
            var chosen = selectBest(containerWidth, containerHeight, images[i]);
            chosenImages.push(chosen);
          } else {
            // In case a new image was pushed in, process it:
            if (typeof images[i] === 'string') {
                images[i] = { url: images[i] };
            }
  
            var item = $.extend({}, images[i]);
            item.url = replaceTagsInUrl(item.url, templateReplacer);
            chosenImages.push(item);
          }
        }
        return chosenImages;
      };
  
    })();
    
    var isVideoSource = function (source) {
      return YOUTUBE_REGEXP.test(source.url) || source.isVideo;
    };
  
    /* Preload images */
    var preload = (function (sources, startAt, count, batchSize, callback) {
      // Plugin cache
      var cache = [];
  
      // Wrapper for cache
      var caching = function(image){
        for (var i = 0; i < cache.length; i++) {
          if (cache[i].src === image.src) {
            return cache[i];
          }
        }
        cache.push(image);
        return image;
      };
  
      // Execute callback
      var exec = function(sources, callback, last){
        if (typeof callback === 'function') {
          callback.call(sources, last);
        }
      };
  
      // Closure to hide cache
      return function preload (sources, startAt, count, batchSize, callback){
        // Check input data
        if (typeof sources === 'undefined') {
          return;
        }
        if (!$.isArray(sources)) {
          sources = [sources];
        }
  
        if (arguments.length < 5 && typeof arguments[arguments.length - 1] === 'function') {
          callback = arguments[arguments.length - 1];
        }
  
        startAt = (typeof startAt === 'function' || !startAt) ? 0 : startAt;
        count = (typeof count === 'function' || !count || count < 0) ? sources.length : Math.min(count, sources.length);
        batchSize = (typeof batchSize === 'function' || !batchSize) ? 1 : batchSize;
  
        if (startAt >= sources.length) {
            startAt = 0;
            count = 0;
        }
        if (batchSize < 0) {
            batchSize = count;
        }
        batchSize = Math.min(batchSize, count);
  
        var next = sources.slice(startAt + batchSize, count - batchSize);
        sources = sources.slice(startAt, batchSize);
        count = sources.length;
  
        // If sources array is empty
        if (!count) {
          exec(sources, callback, true);
          return;
        }
  
        // Image loading callback
        var countLoaded = 0;
  
        var loaded = function() {
          countLoaded++;
          if (countLoaded !== count) {
            return;
          }
  
          exec(sources, callback, !next);
          preload(next, 0, 0, batchSize, callback);
        };
  
        // Loop sources to preload
        var image;
  
        for (var i = 0; i < sources.length; i++) {
          
          if (isVideoSource(sources[i])) {
            
            // Do not preload videos. There are issues with that.
            // First - we need to keep an instance of the preloaded and use that exactly, not a copy.
            // Second - there are memory issues.
            // If there will be a requirement from users - I'll try to implement this.
  
            continue;
              
          } else {
        
            image = new Image();
            image.src = sources[i].url;
  
            image = caching(image);
  
            if (image.complete) {
              loaded();
            } else {
              $(image).on('load error', loaded);
            }
              
          }
          
        }
      };
    })();
  
    /* Process images array */
    var processImagesArray = function (images) {
      var processed = [];
      for (var i = 0; i < images.length; i++) {
        if (typeof images[i] === 'string') {
          processed.push({ url: images[i] });
        }
        else if ($.isArray(images[i])) {
          processed.push(processImagesArray(images[i]));
        }
        else {
          processed.push(processOptions(images[i]));
        }
      }
      return processed;
    };
  
    /* Process options */
    var processOptions = function (options, required) {
  
      // Convert old options
  
      // centeredX/centeredY are deprecated
      if (options.centeredX || options.centeredY) {
        if (window.console && window.console.log) {
          window.console.log('jquery.backstretch: `centeredX`/`centeredY` is deprecated, please use `alignX`/`alignY`');
        }
        if (options.centeredX) {
          options.alignX = 0.5;
        }
        if (options.centeredY) {
          options.alignY = 0.5;
        }
      }
  
      // Deprecated spec
      if (options.speed !== undefined) {
  
        if (window.console && window.console.log) {
          window.console.log('jquery.backstretch: `speed` is deprecated, please use `transitionDuration`');
        }
  
        options.transitionDuration = options.speed;
        options.transition = 'fade';
      }
  
      // Typo
      if (options.resolutionChangeRatioTreshold !== undefined) {
        window.console.log('jquery.backstretch: `treshold` is a typo!');
        options.resolutionChangeRatioThreshold = options.resolutionChangeRatioTreshold;
      }
  
      // Current spec that needs processing
  
      if (options.fadeFirst !== undefined) {
        options.animateFirst = options.fadeFirst;
      }
  
      if (options.fade !== undefined) {
        options.transitionDuration = options.fade;
        options.transition = 'fade';
      }
  
      return processAlignOptions(options);
    };
  
    /* Process align options */
    var processAlignOptions = function (options, required) {
      if (options.alignX === 'left') {
        options.alignX = 0.0;
      }
      else if (options.alignX === 'center') {
        options.alignX = 0.5;
      }
      else if (options.alignX === 'right') {
        options.alignX = 1.0;
      }
      else {
        if (options.alignX !== undefined || required) {
          options.alignX = parseFloat(options.alignX);
          if (isNaN(options.alignX)) {
            options.alignX = 0.5;
          }
        }
      }
  
      if (options.alignY === 'top') {
        options.alignY = 0.0;
      }
      else if (options.alignY === 'center') {
        options.alignY = 0.5;
      }
      else if (options.alignY === 'bottom') {
        options.alignY = 1.0;
      }
      else {
        if (options.alignX !== undefined || required) {
          options.alignY = parseFloat(options.alignY);
          if (isNaN(options.alignY)) {
            options.alignY = 0.5;
          }
        }
      }
  
      return options;
    };
  
    /* CLASS DEFINITION
     * ========================= */
    var Backstretch = function (container, images, options) {
      this.options = $.extend({}, $.fn.backstretch.defaults, options || {});
  
      this.firstShow = true;
  
      // Process options
      processOptions(this.options, true);
  
      /* In its simplest form, we allow Backstretch to be called on an image path.
       * e.g. $.backstretch('/path/to/image.jpg')
       * So, we need to turn this back into an array.
       */
      this.images = processImagesArray($.isArray(images) ? images : [images]);
  
      /**
       * Paused-Option
       */
      if (this.options.paused) {
          this.paused = true;
      }
  
      /**
       * Start-Option (Index)
       */
      if (this.options.start >= this.images.length)
      {
          this.options.start = this.images.length - 1;
      }
      if (this.options.start < 0)
      {
          this.options.start = 0;
      }
  
      // Convenience reference to know if the container is body.
      this.isBody = container === document.body;
  
      /* We're keeping track of a few different elements
       *
       * Container: the element that Backstretch was called on.
       * Wrap: a DIV that we place the image into, so we can hide the overflow.
       * Root: Convenience reference to help calculate the correct height.
       */
      var $window = $(window);
      this.$container = $(container);
      this.$root = this.isBody ? supportsFixedPosition ? $window : $(document) : this.$container;
  
      this.originalImages = this.images;
      this.images = optimalSizeImages(
          this.options.alwaysTestWindowResolution ? $window : this.$root, 
          this.originalImages);
  
      /**
       * Pre-Loading.
       * This is the first image, so we will preload a minimum of 1 images.
       */
      preload(this.images, this.options.start || 0, this.options.preload || 1);
  
      // Don't create a new wrap if one already exists (from a previous instance of Backstretch)
      var $existing = this.$container.children(".backstretch").first();
      this.$wrap = $existing.length ? $existing : 
          $('<div class="backstretch"></div>')
          .css(this.options.bypassCss ? {} : styles.wrap)
          .appendTo(this.$container);
  
      if (!this.options.bypassCss) {
          
          // Non-body elements need some style adjustments
          if (!this.isBody) {
            // If the container is statically positioned, we need to make it relative,
            // and if no zIndex is defined, we should set it to zero.
            var position = this.$container.css('position')
              , zIndex = this.$container.css('zIndex');
  
            this.$container.css({
                position: position === 'static' ? 'relative' : position
              , zIndex: zIndex === 'auto' ? 0 : zIndex
            });
  
            // Needs a higher z-index
            this.$wrap.css({zIndex: -999998});
          }
  
          // Fixed or absolute positioning?
          this.$wrap.css({
              position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute'
          });
      
      }
  
      // Set the first image
      this.index = this.options.start;
      this.show(this.index);
  
      // Listen for resize
      $window.on('resize.backstretch', $.proxy(this.resize, this))
             .on('orientationchange.backstretch', $.proxy(function () {
                // Need to do this in order to get the right window height
                if (this.isBody && window.pageYOffset === 0) {
                  window.scrollTo(0, 1);
                  this.resize();
                }
             }, this));
    };
  
    var performTransition = function (options) {
  
      var transition = options.transition || 'fade';
  
      // Look for multiple options
      if (typeof transition === 'string' && transition.indexOf('|') > -1) {
        transition = transition.split('|');
      }
  
      if (transition instanceof Array) {
        transition = transition[Math.round(Math.random() * (transition.length - 1))];
      }
      
      var $new = options['new'];
      var $old = options['old'] ? options['old'] : $([]);
  
      switch (transition.toString().toLowerCase()) {
  
        default:
        case 'fade':
          $new.fadeIn({
            duration: options.duration,
            complete: options.complete,
            easing: options.easing || undefined
          });
          break;
          
        case 'fadeinout':
        case 'fade_in_out':
              
          var fadeInNew = function () {
              $new.fadeIn({
                duration: options.duration / 2,
                complete: options.complete,
                easing: options.easing || undefined
              });
          };
          
          if ($old.length) {
              $old.fadeOut({
                duration: options.duration / 2,
                complete: fadeInNew,
                easing: options.easing || undefined
              });
          } else {
              fadeInNew();
          }
          
          break;
  
        case 'pushleft':
        case 'push_left':
        case 'pushright':
        case 'push_right':
        case 'pushup':
        case 'push_up':
        case 'pushdown':
        case 'push_down':
        case 'coverleft':
        case 'cover_left':
        case 'coverright':
        case 'cover_right':
        case 'coverup':
        case 'cover_up':
        case 'coverdown':
        case 'cover_down':
  
          var transitionParts = transition.match(/^(cover|push)_?(.*)$/);
  
          var animProp = transitionParts[2] === 'left' ? 'right' :
              transitionParts[2] === 'right' ? 'left' :
                  transitionParts[2] === 'down' ? 'top' :
                      transitionParts[2] === 'up' ? 'bottom' :
                          'right';
  
          var newCssStart = {
            'display': ''
          }, newCssAnim = {};
          newCssStart[animProp] = '-100%';
          newCssAnim[animProp] = 0;
  
          $new
              .css(newCssStart)
              .animate(newCssAnim, {
                duration: options.duration,
                complete: function () {
                  $new.css(animProp, '');
                  options.complete.apply(this, arguments);
                },
                easing: options.easing || undefined
              });
  
          if (transitionParts[1] === 'push' && $old.length) {
              var oldCssAnim = {};
              oldCssAnim[animProp] = '100%';
  
              $old
                  .animate(oldCssAnim, {
                    duration: options.duration,
                    complete: function () {
                      $old.css('display', 'none');
                    },
                    easing: options.easing || undefined
                  });
          }
  
          break;
      }
  
    };
  
    /* PUBLIC METHODS
     * ========================= */
    Backstretch.prototype = {
  
        resize: function () {
          try {
  
            // Check for a better suited image after the resize
            var $resTest = this.options.alwaysTestWindowResolution ? $(window) : this.$root;
            var newContainerWidth = $resTest.width();
            var newContainerHeight = $resTest.height();
            var changeRatioW = newContainerWidth / (this._lastResizeContainerWidth || 0);
            var changeRatioH = newContainerHeight / (this._lastResizeContainerHeight || 0);
            var resolutionChangeRatioThreshold = this.options.resolutionChangeRatioThreshold || 0.0;
  
            // check for big changes in container size
            if ((newContainerWidth !== this._lastResizeContainerWidth ||
                 newContainerHeight !== this._lastResizeContainerHeight) &&
                ((Math.abs(changeRatioW - 1) >= resolutionChangeRatioThreshold || isNaN(changeRatioW)) ||
                (Math.abs(changeRatioH - 1) >= resolutionChangeRatioThreshold || isNaN(changeRatioH)))) {
  
              this._lastResizeContainerWidth = newContainerWidth;
              this._lastResizeContainerHeight = newContainerHeight;
  
              // Big change: rebuild the entire images array
              this.images = optimalSizeImages($resTest, this.originalImages);
  
              // Preload them (they will be automatically inserted on the next cycle)
              if (this.options.preload) {
                preload(this.images, (this.index + 1) % this.images.length, this.options.preload);
              }
  
              // In case there is no cycle and the new source is different than the current
              if (this.images.length === 1 &&
                  this._currentImage.url !== this.images[0].url) {
  
                // Wait a little an update the image being showed
                var that = this;
                clearTimeout(that._selectAnotherResolutionTimeout);
                that._selectAnotherResolutionTimeout = setTimeout(function () {
                  that.show(0);
                }, this.options.resolutionRefreshRate);
              }
            }
  
            var bgCSS = {left: 0, top: 0, right: 'auto', bottom: 'auto'}
              , rootWidth = this.isBody ? this.$root.width() : this.$root.innerWidth()
              , rootHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight()
              , bgWidth = rootWidth
              , bgHeight = bgWidth / this.$itemWrapper.data('ratio')
              , evt = $.Event('backstretch.resize', {
                relatedTarget: this.$container[0]
              })
              , bgOffset
              , alignX = this._currentImage.alignX === undefined ? this.options.alignX : this._currentImage.alignX
              , alignY = this._currentImage.alignY === undefined ? this.options.alignY : this._currentImage.alignY;
  
              // Make adjustments based on image ratio
              if (bgHeight >= rootHeight) {
                  bgCSS.top = -(bgHeight - rootHeight) * alignY;
              } else {
                  bgHeight = rootHeight;
                  bgWidth = bgHeight * this.$itemWrapper.data('ratio');
                  bgOffset = (bgWidth - rootWidth) / 2;
                  bgCSS.left = -(bgWidth - rootWidth) * alignX;
              }
  
              if (!this.options.bypassCss) {
  
                  this.$wrap
                      .css({width: rootWidth, height: rootHeight})
                      .find('>.backstretch-item').not('.deleteable')
                      .each(function () {
                          var $wrapper = $(this);
                          $wrapper.find('img,video,iframe')
                                  .css({width: bgWidth, height: bgHeight})
                                  .css(bgCSS);
                      });
              }
  
              this.$container.trigger(evt, this);
          } catch(err) {
              // IE7 seems to trigger resize before the image is loaded.
              // This try/catch block is a hack to let it fail gracefully.
          }
  
          return this;
        }
  
        // Show the slide at a certain position
      , show: function (newIndex, overrideOptions) {
  
          // Validate index
          if (Math.abs(newIndex) > this.images.length - 1) {
            return;
          }
  
          // Vars
          var that = this
            , $oldItemWrapper = that.$wrap.find('>.backstretch-item').addClass('deleteable')
            , oldVideoWrapper = that.videoWrapper
            , evtOptions = { relatedTarget: that.$container[0] };
  
          // Trigger the "before" event
          that.$container.trigger($.Event('backstretch.before', evtOptions), [that, newIndex]);
  
          // Set the new frame index
          this.index = newIndex;
          var selectedImage = that.images[newIndex];
  
          // Pause the slideshow
          clearTimeout(that._cycleTimeout);
  
          // New image
  
          delete that.videoWrapper; // Current item may not be a video
  
          var isVideo = isVideoSource(selectedImage);
          if (isVideo) {
            that.videoWrapper = new VideoWrapper(selectedImage);
            that.$item = that.videoWrapper.$video.css('pointer-events', 'none');
          } else {
            that.$item = $('<img />');
          }
  
          that.$itemWrapper = $('<div class="backstretch-item">')
              .append(that.$item);
  
          if (this.options.bypassCss) {
              that.$itemWrapper.css({
                'display': 'none'
              });
          } else {
            that.$itemWrapper.css(styles.itemWrapper);
            that.$item.css(styles.item);
          }
  
          that.$item.bind(isVideo ? 'canplay' : 'load', function (e) {
              var $this = $(this)
                , $wrapper = $this.parent()
                , options = $wrapper.data('options');
                
              if (overrideOptions) {
                options = $.extend({}, options, overrideOptions);
              }
  
              var imgWidth = this.naturalWidth || this.videoWidth || this.width
                , imgHeight = this.naturalHeight || this.videoHeight || this.height;
  
              // Save the ratio
              $wrapper.data('ratio', imgWidth / imgHeight);
  
              var getOption = function (opt) {
                return options[opt] !== undefined ?
                  options[opt] :
                  that.options[opt];
              };
  
              var transition = getOption('transition');
              var transitionEasing = getOption('transitionEasing');
              var transitionDuration = getOption('transitionDuration');
  
              // Show the image, then delete the old one
              var bringInNextImage = function () {
                
                if (oldVideoWrapper) {
                  oldVideoWrapper.stop();
                  oldVideoWrapper.destroy();
                }
                
                $oldItemWrapper.remove();
  
                // Resume the slideshow
                if (!that.paused && that.images.length > 1) {
                  that.cycle();
                }
  
                // Now we can clear the background on the element, to spare memory
                if (!that.options.bypassCss && !that.isBody) {
                  that.$container.css('background-image', 'none');
                }
  
                // Trigger the "after" and "show" events
                // "show" is being deprecated
                $(['after', 'show']).each(function () {
                  that.$container.trigger($.Event('backstretch.' + this, evtOptions), [that, newIndex]);
                });
                
                if (isVideo) {
                  that.videoWrapper.play();
                }
              };
  
              if ((that.firstShow && !that.options.animateFirst) || !transitionDuration || !transition) {
                  // Avoid transition on first show or if there's no transitionDuration value
                  $wrapper.show();
                  bringInNextImage();
              } else {
  
                  performTransition({
                      'new': $wrapper,
                      old: $oldItemWrapper,
                      transition: transition,
                      duration: transitionDuration,
                      easing: transitionEasing,
                      complete: bringInNextImage
                  });
  
              }
  
              that.firstShow = false;
  
              // Resize
              that.resize();
          });
  
          that.$itemWrapper.appendTo(that.$wrap);
  
          that.$item.attr('alt', selectedImage.alt || '');
          that.$itemWrapper.data('options', selectedImage);
  
          if (!isVideo) {
            that.$item.attr('src', selectedImage.url);
          }
          
          that._currentImage = selectedImage;
  
          return that;
        }
  
      , current: function () {
          return this.index;
        }
  
      , next: function () {
          var args = Array.prototype.slice.call(arguments, 0);
          args.unshift(this.index < this.images.length - 1 ? this.index + 1 : 0);
          return this.show.apply(this, args);
        }
  
      , prev: function () {
          var args = Array.prototype.slice.call(arguments, 0);
          args.unshift(this.index === 0 ? this.images.length - 1 : this.index - 1);
          return this.show.apply(this, args);
        }
  
      , pause: function () {
          // Pause the slideshow
          this.paused = true;
          
          if (this.videoWrapper) {
            this.videoWrapper.pause();
          }
          
          return this;
        }
  
      , resume: function () {
          // Resume the slideshow
          this.paused = false;
          
          if (this.videoWrapper) {
            this.videoWrapper.play();
          }
          
          this.cycle();
          return this;
        }
  
      , cycle: function () {
          // Start/resume the slideshow
          if(this.images.length > 1) {
            // Clear the timeout, just in case
            clearTimeout(this._cycleTimeout);
  
            var duration = (this._currentImage && this._currentImage.duration) || this.options.duration;
            var isVideo = isVideoSource(this._currentImage);
            
            var callNext = function () {
              this.$item.off('.cycle');
              
              // Check for paused slideshow
              if (!this.paused) {
                this.next();
              }
            };
  
            // Special video handling
            if (isVideo) {
  
              // Leave video at last frame
              if (!this._currentImage.loop) {
                var lastFrameTimeout = 0;
  
                this.$item
                  .on('playing.cycle', function () {
                    var player = $(this).data('player');
  
                    clearTimeout(lastFrameTimeout);
                    lastFrameTimeout = setTimeout(function () {
                      player.pause();
                      player.$video.trigger('ended');
                    }, (player.getDuration() - player.getCurrentTime()) * 1000);
                  })
                  .on('ended.cycle', function () {
                    clearTimeout(lastFrameTimeout);
                  });
              }
  
              // On error go to next
              this.$item.on('error.cycle initerror.cycle', $.proxy(callNext, this));
            }
  
            if (isVideo && !this._currentImage.duration) {
              // It's a video - playing until end
              this.$item.on('ended.cycle', $.proxy(callNext, this));
              
            } else {
              // Cycling according to specified duration
              this._cycleTimeout = setTimeout($.proxy(callNext, this), duration);
            }
            
          }
          return this;
        }
  
      , destroy: function (preserveBackground) {
          // Stop the resize events
          $(window).off('resize.backstretch orientationchange.backstretch');
  
          // Stop any videos
          if (this.videoWrapper) {
            this.videoWrapper.destroy();
          }
          
          // Clear the timeout
          clearTimeout(this._cycleTimeout);
  
          // Remove Backstretch
          if(!preserveBackground) {
            this.$wrap.remove();
          }
          this.$container.removeData('backstretch');
        }
    };
      
   /**
    * Video Abstraction Layer
    *
    * Static methods:
    * > VideoWrapper.loadYoutubeAPI() -> Call in order to load the Youtube API. 
    *                                   An 'youtube_api_load' event will be triggered on $(window) when the API is loaded.
    *
    * Generic:
    * > player.type -> type of the video
    * > player.video / player.$video -> contains the element holding the video
    * > player.play() -> plays the video
    * > player.pause() -> pauses the video
    * > player.setCurrentTime(position) -> seeks to a position by seconds
    * 
    * Youtube:
    * > player.ytId will contain the youtube ID if the source is a youtube url
    * > player.ytReady is a flag telling whether the youtube source is ready for playback
    * */
  
    var VideoWrapper = function () { this.init.apply(this, arguments); };
  
    /**
     * @param {Object} options
     * @param {String|Array<String>|Array<{{src: String, type: String?}}>} options.url
     * @param {Boolean} options.loop=false
     * @param {Boolean?} options.mute=true
     * @param {String?} options.poster
     * loop, mute, poster
     */
    VideoWrapper.prototype.init = function (options) {
  
      var that = this;
      
      var $video;
  
      var setVideoElement = function () {
        that.$video = $video;
        that.video = $video[0];
      };
      
      // Determine video type
      
      var videoType = 'video';
      
      if (!(options.url instanceof Array) &&
        YOUTUBE_REGEXP.test(options.url)) {
        videoType = 'youtube';
      }
      
      that.type = videoType;
  
      if (videoType === 'youtube') {
  
        // Try to load the API in the meantime
        VideoWrapper.loadYoutubeAPI();
  
        that.ytId = options.url.match(YOUTUBE_REGEXP)[2];
        var src = 'https://www.youtube.com/embed/' + that.ytId +
          '?rel=0&autoplay=0&showinfo=0&controls=0&modestbranding=1' +
          '&cc_load_policy=0&disablekb=1&iv_load_policy=3&loop=0' +
          '&enablejsapi=1&origin=' + encodeURIComponent(window.location.origin);
  
        that.__ytStartMuted = !!options.mute || options.mute === undefined;
  
        $video = $('<iframe />')
          .attr({ 'src_to_load': src })
          .css({ 'border': 0, 'margin': 0, 'padding': 0 })
          .data('player', that);
          
        if (options.loop) {
          $video.on('ended.loop', function () {
            if (!that.__manuallyStopped) {
             that.play();
            }
          });
        }
  
        that.ytReady = false;
  
        setVideoElement();
  
        if (window['YT']) {
          that._initYoutube();
          $video.trigger('initsuccess');
        } else {
          $(window).one('youtube_api_load', function () {
            that._initYoutube();
            $video.trigger('initsuccess');
          });
        }
        
      }
      else {
        // Traditional <video> tag with multiple sources
        
        $video = $('<video>')
          .prop('autoplay', false)
          .prop('controls', false)
          .prop('loop', !!options.loop)
          .prop('muted', !!options.mute || options.mute === undefined)
          
          // Let the first frames be available before playback, as we do transitions
          .prop('preload', 'auto')
          .prop('poster', options.poster || '');
          
        var sources = (options.url instanceof Array) ? options.url : [options.url];
  
        for (var i = 0; i < sources.length; i++) {
          var sourceItem = sources[i];
          if (typeof(sourceItem) === 'string') {
            sourceItem = { src: sourceItem };
          }
          $('<source>')
            .attr('src', sourceItem.src)
            // Make sure to not specify type if unknown - 
            //   so the browser will try to autodetect.
            .attr('type', sourceItem.type || null)
            .appendTo($video);
        }
        
        if (!$video[0].canPlayType || !sources.length) {
          $video.trigger('initerror');
        } else {
          $video.trigger('initsuccess');
        }
  
        setVideoElement();
      }
  
    };
  
    VideoWrapper.prototype._initYoutube = function () {
      var that = this;
      
      var YT = window['YT'];
  
      that.$video
        .attr('src', that.$video.attr('src_to_load'))
        .removeAttr('src_to_load');
  
      // It won't init if it's not in the DOM, so we emulate that
      var hasParent = !!that.$video[0].parentNode;
      if (!hasParent) {
        var $tmpParent = $('<div>').css('display', 'none !important').appendTo(document.body);
        that.$video.appendTo($tmpParent);
      }
  
      var player = new YT.Player(that.video, {
        events: {
          'onReady': function () {
  
            if (that.__ytStartMuted) {
              player.mute();
            }
  
            if (!hasParent) {
              // Restore parent to old state - without interrupting any changes
              if (that.$video[0].parentNode === $tmpParent[0]) {
                that.$video.detach();
              }
              $tmpParent.remove();
            }
  
            that.ytReady = true;
            that._updateYoutubeSize();
            that.$video.trigger('canplay');
          },
          'onStateChange': function (event) {
            switch (event.data) {
              case YT.PlayerState.PLAYING:
                that.$video.trigger('playing');
                break;
              case YT.PlayerState.ENDED:
                that.$video.trigger('ended');
                break;
              case YT.PlayerState.PAUSED:
                that.$video.trigger('pause');
                break;
              case YT.PlayerState.BUFFERING:
                that.$video.trigger('waiting');
                break;
              case YT.PlayerState.CUED:
                that.$video.trigger('canplay');
                break;
            }
          },
          'onPlaybackQualityChange': function () {
            that._updateYoutubeSize();
            that.$video.trigger('resize');
          },
          'onError': function (err) {
            that.hasError = true;
            that.$video.trigger({ 'type': 'error', 'error': err });
          }
        }
      });
  
      that.ytPlayer = player;
  
      return that;
    };    
      
    VideoWrapper.prototype._updateYoutubeSize = function () {
      var that = this;
  
      switch (that.ytPlayer.getPlaybackQuality() || 'medium') {
        case 'small':
          that.video.videoWidth = 426;
          that.video.videoHeight = 240;
          break;
        case 'medium':
          that.video.videoWidth = 640;
          that.video.videoHeight = 360;
          break;
        default:
        case 'large':
          that.video.videoWidth = 854;
          that.video.videoHeight = 480;
          break;
        case 'hd720':
          that.video.videoWidth = 1280;
          that.video.videoHeight = 720;
          break;
        case 'hd1080':
          that.video.videoWidth = 1920;
          that.video.videoHeight = 1080;
          break;
        case 'highres':
          that.video.videoWidth = 2560;
          that.video.videoHeight = 1440;
          break;
      }
  
      return that;
    };
  
    VideoWrapper.prototype.play = function () {
      var that = this;
  
      that.__manuallyStopped = false;
      
      if (that.type === 'youtube') {
        if (that.ytReady) {
          that.$video.trigger('play');
          that.ytPlayer.playVideo();
        }
      } else {
        that.video.play();
      }
  
      return that;
    };
  
    VideoWrapper.prototype.pause = function () {
      var that = this;
  
      that.__manuallyStopped = false;
      
      if (that.type === 'youtube') {
        if (that.ytReady) {
          that.ytPlayer.pauseVideo();
        }
      } else {
        that.video.pause();
      }
  
      return that;
    };
  
    VideoWrapper.prototype.stop = function () {
      var that = this;
  
      that.__manuallyStopped = true;
      
      if (that.type === 'youtube') {
        if (that.ytReady) {
          that.ytPlayer.pauseVideo();
          that.ytPlayer.seekTo(0);
        }
      } else {
        that.video.pause();
        that.video.currentTime = 0;
      }
  
      return that;
    };
  
    VideoWrapper.prototype.destroy = function () {
      var that = this;
  
      if (that.ytPlayer) {
        that.ytPlayer.destroy();
      }
  
      that.$video.remove();
  
      return that;
    };
  
    VideoWrapper.prototype.getCurrentTime = function (seconds) {
      var that = this;
  
      if (that.type === 'youtube') {
        if (that.ytReady) {
          return that.ytPlayer.getCurrentTime();
        }
      } else {
        return that.video.currentTime;
      }
  
      return 0;
    };
  
    VideoWrapper.prototype.setCurrentTime = function (seconds) {
      var that = this;
  
      if (that.type === 'youtube') {
        if (that.ytReady) {
          that.ytPlayer.seekTo(seconds, true);
        }
      } else {
        that.video.currentTime = seconds;
      }
  
      return that;
    };
  
    VideoWrapper.prototype.getDuration = function () {
      var that = this;
  
      if (that.type === 'youtube') {
        if (that.ytReady) {
          return that.ytPlayer.getDuration();
        }
      } else {
        return that.video.duration;
      }
  
      return 0;
    };
  
    /**
     * This will load the youtube API (if not loaded yet)
     * Use $(window).one('youtube_api_load', ...) to listen for API loaded event
     */
    VideoWrapper.loadYoutubeAPI = function () {
      if (window['YT']) {
        return;
      }
      if (!$('script[src*=www\\.youtube\\.com\\/iframe_api]').length) {
        $('<script type="text/javascript" src="https://www.youtube.com/iframe_api">').appendTo('body');
      }
      var ytAPILoadInt = setInterval(function () {
        if (window['YT'] && window['YT'].loaded) {
          $(window).trigger('youtube_api_load');
          clearTimeout(ytAPILoadInt);
        }
      }, 50);
    };
  
    var getDeviceOrientation = function () {
  
      if ('matchMedia' in window) {
        if (window.matchMedia("(orientation: portrait)").matches) {
           return 'portrait';
        } else if (window.matchMedia("(orientation: landscape)").matches) {
           return 'landscape';
        }
      }
  
      if (screen.height > screen.width) {
        return 'portrait';
      }
  
      // Even square devices have orientation,
      //   but a desktop browser may be too old for `matchMedia`.
      // Defaulting to `landscape` for the VERY rare case of a square desktop screen is good enough.
      return 'landscape';
    };
  
    var getWindowOrientation = function () {
      if (window.innerHeight > window.innerWidth) {
        return 'portrait';
      }
      if (window.innerWidth > window.innerHeight) {
        return 'landscape';
      }
  
      return 'square';
    };
  
    /* SUPPORTS FIXED POSITION?
     *
     * Based on code from jQuery Mobile 1.1.0
     * http://jquerymobile.com/
     *
     * In a nutshell, we need to figure out if fixed positioning is supported.
     * Unfortunately, this is very difficult to do on iOS, and usually involves
     * injecting content, scrolling the page, etc.. It's ugly.
     * jQuery Mobile uses this workaround. It's not ideal, but works.
     *
     * Modified to detect IE6
     * ========================= */
  
    var supportsFixedPosition = (function () {
      var ua = navigator.userAgent
        , platform = navigator.platform
          // Rendering engine is Webkit, and capture major version
        , wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ )
        , wkversion = !!wkmatch && wkmatch[ 1 ]
        , ffmatch = ua.match( /Fennec\/([0-9]+)/ )
        , ffversion = !!ffmatch && ffmatch[ 1 ]
        , operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ )
        , omversion = !!operammobilematch && operammobilematch[ 1 ]
        , iematch = ua.match( /MSIE ([0-9]+)/ )
        , ieversion = !!iematch && iematch[ 1 ];
  
      return !(
        // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
        ((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1  || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) ||
  
        // Opera Mini
        (window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") ||
        (operammobilematch && omversion < 7458) ||
  
        //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
        (ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) ||
  
        // Firefox Mobile before 6.0 -
        (ffversion && ffversion < 6) ||
  
        // WebOS less than 3
        ("palmGetResource" in window && wkversion && wkversion < 534) ||
  
        // MeeGo
        (ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) ||
  
        // IE6
        (ieversion && ieversion <= 6)
      );
    }());
  
  }(jQuery, window));