text: /*
   * blueimp Gallery JS
   * https://github.com/blueimp/Gallery
   *
   * Copyright 2013, Sebastian Tschan
   * https://blueimp.net
   *
   * Swipe implementation based on
   * https://github.com/bradbirdsall/Swipe
   *
   * Licensed under the MIT license:
   * https://opensource.org/licenses/MIT
   */
  
  /* global define, DocumentTouch */
  
  /* eslint-disable no-param-reassign */
  
  ;(function (factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
      // Register as an anonymous AMD module:
      define(['./blueimp-helper'], factory);
    } else {
      // Browser globals:
      window.blueimp = window.blueimp || {};
      window.blueimp.Gallery = factory(window.blueimp.helper || window.jQuery);
    }
  })(function ($) {
    'use strict';
  
    /**
     * Gallery constructor
     *
     * @class
     * @param {Array|NodeList} list Gallery content
     * @param {object} [options] Gallery options
     * @returns {object} Gallery object
     */
    function Gallery(list, options) {
      if (document.body.style.maxHeight === undefined) {
        // document.body.style.maxHeight is undefined on IE6 and lower
        return null;
      }
      if (!this || this.options !== Gallery.prototype.options) {
        // Called as function instead of as constructor,
        // so we simply return a new instance:
        return new Gallery(list, options);
      }
      if (!list || !list.length) {
        this.console.log(
          'blueimp Gallery: No or empty list provided as first argument.',
          list
        );
        return;
      }
      this.list = list;
      this.num = list.length;
      this.initOptions(options);
      this.initialize();
    }
  
    $.extend(Gallery.prototype, {
      options: {
        // The Id, element or querySelector of the gallery widget:
        container: '#blueimp-gallery',
        // The tag name, Id, element or querySelector of the slides container:
        slidesContainer: 'div',
        // The tag name, Id, element or querySelector of the title element:
        titleElement: 'h3',
        // The class to add when the gallery is visible:
        displayClass: 'blueimp-gallery-display',
        // The class to add when the gallery controls are visible:
        controlsClass: 'blueimp-gallery-controls',
        // The class to add when the gallery only displays one element:
        singleClass: 'blueimp-gallery-single',
        // The class to add when the left edge has been reached:
        leftEdgeClass: 'blueimp-gallery-left',
        // The class to add when the right edge has been reached:
        rightEdgeClass: 'blueimp-gallery-right',
        // The class to add when the automatic slideshow is active:
        playingClass: 'blueimp-gallery-playing',
        // The class for all slides:
        slideClass: 'slide',
        // The slide class for loading elements:
        slideLoadingClass: 'slide-loading',
        // The slide class for elements that failed to load:
        slideErrorClass: 'slide-error',
        // The class for the content element loaded into each slide:
        slideContentClass: 'slide-content',
        // The class for the "toggle" control:
        toggleClass: 'toggle',
        // The class for the "prev" control:
        prevClass: 'prev',
        // The class for the "next" control:
        nextClass: 'next',
        // The class for the "close" control:
        closeClass: 'close',
        // The class for the "play-pause" toggle control:
        playPauseClass: 'play-pause',
        // The list object property (or data attribute) with the object type:
        typeProperty: 'type',
        // The list object property (or data attribute) with the object title:
        titleProperty: 'title',
        // The list object property (or data attribute) with the object alt text:
        altTextProperty: 'alt',
        // The list object property (or data attribute) with the object URL:
        urlProperty: 'href',
        // The list object property (or data attribute) with the object srcset URL(s):
        srcsetProperty: 'urlset',
        // The gallery listens for transitionend events before triggering the
        // opened and closed events, unless the following option is set to false:
        displayTransition: true,
        // Defines if the gallery slides are cleared from the gallery modal,
        // or reused for the next gallery initialization:
        clearSlides: true,
        // Defines if images should be stretched to fill the available space,
        // while maintaining their aspect ratio (will only be enabled for browsers
        // supporting background-size="contain", which excludes IE < 9).
        // Set to "cover", to make images cover all available space (requires
        // support for background-size="cover", which excludes IE < 9):
        stretchImages: false,
        // Toggle the controls on pressing the Return key:
        toggleControlsOnReturn: true,
        // Toggle the controls on slide click:
        toggleControlsOnSlideClick: true,
        // Toggle the automatic slideshow interval on pressing the Space key:
        toggleSlideshowOnSpace: true,
        // Navigate the gallery by pressing left and right on the keyboard:
        enableKeyboardNavigation: true,
        // Close the gallery on pressing the Esc key:
        closeOnEscape: true,
        // Close the gallery when clicking on an empty slide area:
        closeOnSlideClick: true,
        // Close the gallery by swiping up or down:
        closeOnSwipeUpOrDown: true,
        // Emulate touch events on mouse-pointer devices such as desktop browsers:
        emulateTouchEvents: true,
        // Stop touch events from bubbling up to ancestor elements of the Gallery:
        stopTouchEventsPropagation: false,
        // Hide the page scrollbars:
        hidePageScrollbars: true,
        // Stops any touches on the container from scrolling the page:
        disableScroll: true,
        // Carousel mode (shortcut for carousel specific options):
        carousel: false,
        // Allow continuous navigation, moving from last to first
        // and from first to last slide:
        continuous: true,
        // Remove elements outside of the preload range from the DOM:
        unloadElements: true,
        // Start with the automatic slideshow:
        startSlideshow: false,
        // Delay in milliseconds between slides for the automatic slideshow:
        slideshowInterval: 5000,
        // The direction the slides are moving: ltr=LeftToRight or rtl=RightToLeft
        slideshowDirection: 'ltr',
        // The starting index as integer.
        // Can also be an object of the given list,
        // or an equal object with the same url property:
        index: 0,
        // The number of elements to load around the current index:
        preloadRange: 2,
        // The transition speed between slide changes in milliseconds:
        transitionSpeed: 400,
        // The transition speed for automatic slide changes, set to an integer
        // greater 0 to override the default transition speed:
        slideshowTransitionSpeed: undefined,
        // The event object for which the default action will be canceled
        // on Gallery initialization (e.g. the click event to open the Gallery):
        event: undefined,
        // Callback function executed when the Gallery is initialized.
        // Is called with the gallery instance as "this" object:
        onopen: undefined,
        // Callback function executed when the Gallery has been initialized
        // and the initialization transition has been completed.
        // Is called with the gallery instance as "this" object:
        onopened: undefined,
        // Callback function executed on slide change.
        // Is called with the gallery instance as "this" object and the
        // current index and slide as arguments:
        onslide: undefined,
        // Callback function executed after the slide change transition.
        // Is called with the gallery instance as "this" object and the
        // current index and slide as arguments:
        onslideend: undefined,
        // Callback function executed on slide content load.
        // Is called with the gallery instance as "this" object and the
        // slide index and slide element as arguments:
        onslidecomplete: undefined,
        // Callback function executed when the Gallery is about to be closed.
        // Is called with the gallery instance as "this" object:
        onclose: undefined,
        // Callback function executed when the Gallery has been closed
        // and the closing transition has been completed.
        // Is called with the gallery instance as "this" object:
        onclosed: undefined
      },
  
      carouselOptions: {
        hidePageScrollbars: false,
        toggleControlsOnReturn: false,
        toggleSlideshowOnSpace: false,
        enableKeyboardNavigation: false,
        closeOnEscape: false,
        closeOnSlideClick: false,
        closeOnSwipeUpOrDown: false,
        disableScroll: false,
        startSlideshow: true
      },
  
      console:
        window.console && typeof window.console.log === 'function'
          ? window.console
          : { log: function () {} },
  
      // Detect touch, transition, transform and background-size support:
      support: (function (element) {
        var support = {
          touch:
            window.ontouchstart !== undefined ||
            (window.DocumentTouch && document instanceof DocumentTouch)
        };
        var transitions = {
          webkitTransition: {
            end: 'webkitTransitionEnd',
            prefix: '-webkit-'
          },
          MozTransition: {
            end: 'transitionend',
            prefix: '-moz-'
          },
          OTransition: {
            end: 'otransitionend',
            prefix: '-o-'
          },
          transition: {
            end: 'transitionend',
            prefix: ''
          }
        };
        var prop;
        for (prop in transitions) {
          if (
            Object.prototype.hasOwnProperty.call(transitions, prop) &&
            element.style[prop] !== undefined
          ) {
            support.transition = transitions[prop];
            support.transition.name = prop;
            break;
          }
        }
        /**
         * Tests browser support
         */
        function elementTests() {
          var transition = support.transition;
          var prop;
          var translateZ;
          document.body.appendChild(element);
          if (transition) {
            prop = transition.name.slice(0, -9) + 'ransform';
            if (element.style[prop] !== undefined) {
              element.style[prop] = 'translateZ(0)';
              translateZ = window
                .getComputedStyle(element)
                .getPropertyValue(transition.prefix + 'transform');
              support.transform = {
                prefix: transition.prefix,
                name: prop,
                translate: true,
                translateZ: !!translateZ && translateZ !== 'none'
              };
            }
          }
          if (element.style.backgroundSize !== undefined) {
            support.backgroundSize = {};
            element.style.backgroundSize = 'contain';
            support.backgroundSize.contain =
              window
                .getComputedStyle(element)
                .getPropertyValue('background-size') === 'contain';
            element.style.backgroundSize = 'cover';
            support.backgroundSize.cover =
              window
                .getComputedStyle(element)
                .getPropertyValue('background-size') === 'cover';
          }
          document.body.removeChild(element);
        }
        if (document.body) {
          elementTests();
        } else {
          $(document).on('DOMContentLoaded', elementTests);
        }
        return support;
        // Test element, has to be standard HTML and must not be hidden
        // for the CSS3 tests using window.getComputedStyle to be applicable:
      })(document.createElement('div')),
  
      requestAnimationFrame:
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame,
  
      cancelAnimationFrame:
        window.cancelAnimationFrame ||
        window.webkitCancelRequestAnimationFrame ||
        window.webkitCancelAnimationFrame ||
        window.mozCancelAnimationFrame,
  
      initialize: function () {
        this.initStartIndex();
        if (this.initWidget() === false) {
          return false;
        }
        this.initEventListeners();
        // Load the slide at the given index:
        this.onslide(this.index);
        // Manually trigger the slideend event for the initial slide:
        this.ontransitionend();
        // Start the automatic slideshow if applicable:
        if (this.options.startSlideshow) {
          this.play();
        }
      },
  
      slide: function (to, speed) {
        window.clearTimeout(this.timeout);
        var index = this.index;
        var direction;
        var naturalDirection;
        var diff;
        if (index === to || this.num === 1) {
          return;
        }
        if (!speed) {
          speed = this.options.transitionSpeed;
        }
        if (this.support.transform) {
          if (!this.options.continuous) {
            to = this.circle(to);
          }
          // 1: backward, -1: forward:
          direction = Math.abs(index - to) / (index - to);
          // Get the actual position of the slide:
          if (this.options.continuous) {
            naturalDirection = direction;
            direction = -this.positions[this.circle(to)] / this.slideWidth;
            // If going forward but to < index, use to = slides.length + to
            // If going backward but to > index, use to = -slides.length + to
            if (direction !== naturalDirection) {
              to = -direction * this.num + to;
            }
          }
          diff = Math.abs(index - to) - 1;
          // Move all the slides between index and to in the right direction:
          while (diff) {
            diff -= 1;
            this.move(
              this.circle((to > index ? to : index) - diff - 1),
              this.slideWidth * direction,
              0
            );
          }
          to = this.circle(to);
          this.move(index, this.slideWidth * direction, speed);
          this.move(to, 0, speed);
          if (this.options.continuous) {
            this.move(
              this.circle(to - direction),
              -(this.slideWidth * direction),
              0
            );
          }
        } else {
          to = this.circle(to);
          this.animate(index * -this.slideWidth, to * -this.slideWidth, speed);
        }
        this.onslide(to);
      },
  
      getIndex: function () {
        return this.index;
      },
  
      getNumber: function () {
        return this.num;
      },
  
      prev: function () {
        if (this.options.continuous || this.index) {
          this.slide(this.index - 1);
        }
      },
  
      next: function () {
        if (this.options.continuous || this.index < this.num - 1) {
          this.slide(this.index + 1);
        }
      },
  
      play: function (time) {
        var that = this;
        var nextIndex =
          this.index + (this.options.slideshowDirection === 'rtl' ? -1 : 1);
        window.clearTimeout(this.timeout);
        this.interval = time || this.options.slideshowInterval;
        if (this.elements[this.index] > 1) {
          this.timeout = this.setTimeout(
            (!this.requestAnimationFrame && this.slide) ||
              function (to, speed) {
                that.animationFrameId = that.requestAnimationFrame.call(
                  window,
                  function () {
                    that.slide(to, speed);
                  }
                );
              },
            [nextIndex, this.options.slideshowTransitionSpeed],
            this.interval
          );
        }
        this.container.addClass(this.options.playingClass);
      },
  
      pause: function () {
        window.clearTimeout(this.timeout);
        this.interval = null;
        if (this.cancelAnimationFrame) {
          this.cancelAnimationFrame.call(window, this.animationFrameId);
          this.animationFrameId = null;
        }
        this.container.removeClass(this.options.playingClass);
      },
  
      add: function (list) {
        var i;
        if (!list.concat) {
          // Make a real array out of the list to add:
          list = Array.prototype.slice.call(list);
        }
        if (!this.list.concat) {
          // Make a real array out of the Gallery list:
          this.list = Array.prototype.slice.call(this.list);
        }
        this.list = this.list.concat(list);
        this.num = this.list.length;
        if (this.num > 2 && this.options.continuous === null) {
          this.options.continuous = true;
          this.container.removeClass(this.options.leftEdgeClass);
        }
        this.container
          .removeClass(this.options.rightEdgeClass)
          .removeClass(this.options.singleClass);
        for (i = this.num - list.length; i < this.num; i += 1) {
          this.addSlide(i);
          this.positionSlide(i);
        }
        this.positions.length = this.num;
        this.initSlides(true);
      },
  
      resetSlides: function () {
        this.slidesContainer.empty();
        this.unloadAllSlides();
        this.slides = [];
      },
  
      handleClose: function () {
        var options = this.options;
        this.destroyEventListeners();
        // Cancel the slideshow:
        this.pause();
        this.container[0].style.display = 'none';
        this.container
          .removeClass(options.displayClass)
          .removeClass(options.singleClass)
          .removeClass(options.leftEdgeClass)
          .removeClass(options.rightEdgeClass);
        if (options.hidePageScrollbars) {
          document.body.style.overflow = this.bodyOverflowStyle;
        }
        if (this.options.clearSlides) {
          this.resetSlides();
        }
        if (this.options.onclosed) {
          this.options.onclosed.call(this);
        }
      },
  
      close: function () {
        var that = this;
  
        /**
         * Close handler
         *
         * @param {event} event Close event
         */
        function closeHandler(event) {
          if (event.target === that.container[0]) {
            that.container.off(that.support.transition.end, closeHandler);
            that.handleClose();
          }
        }
        if (this.options.onclose) {
          this.options.onclose.call(this);
        }
        if (this.support.transition && this.options.displayTransition) {
          this.container.on(this.support.transition.end, closeHandler);
          this.container.removeClass(this.options.displayClass);
        } else {
          this.handleClose();
        }
      },
  
      circle: function (index) {
        // Always return a number inside of the slides index range:
        return (this.num + (index % this.num)) % this.num;
      },
  
      move: function (index, dist, speed) {
        this.translateX(index, dist, speed);
        this.positions[index] = dist;
      },
  
      translate: function (index, x, y, speed) {
        if (!this.slides[index]) return;
        var style = this.slides[index].style;
        var transition = this.support.transition;
        var transform = this.support.transform;
        style[transition.name + 'Duration'] = speed + 'ms';
        style[transform.name] =
          'translate(' +
          x +
          'px, ' +
          y +
          'px)' +
          (transform.translateZ ? ' translateZ(0)' : '');
      },
  
      translateX: function (index, x, speed) {
        this.translate(index, x, 0, speed);
      },
  
      translateY: function (index, y, speed) {
        this.translate(index, 0, y, speed);
      },
  
      animate: function (from, to, speed) {
        if (!speed) {
          this.slidesContainer[0].style.left = to + 'px';
          return;
        }
        var that = this;
        var start = new Date().getTime();
        var timer = window.setInterval(function () {
          var timeElap = new Date().getTime() - start;
          if (timeElap > speed) {
            that.slidesContainer[0].style.left = to + 'px';
            that.ontransitionend();
            window.clearInterval(timer);
            return;
          }
          that.slidesContainer[0].style.left =
            (to - from) * (Math.floor((timeElap / speed) * 100) / 100) +
            from +
            'px';
        }, 4);
      },
  
      preventDefault: function (event) {
        if (event.preventDefault) {
          event.preventDefault();
        } else {
          event.returnValue = false;
        }
      },
  
      stopPropagation: function (event) {
        if (event.stopPropagation) {
          event.stopPropagation();
        } else {
          event.cancelBubble = true;
        }
      },
  
      onresize: function () {
        this.initSlides(true);
      },
  
      onmousedown: function (event) {
        // Trigger on clicks of the left mouse button only
        // and exclude video & audio elements:
        if (
          event.which &&
          event.which === 1 &&
          event.target.nodeName !== 'VIDEO' &&
          event.target.nodeName !== 'AUDIO'
        ) {
          // Preventing the default mousedown action is required
          // to make touch emulation work with Firefox:
          event.preventDefault()
          ;(event.originalEvent || event).touches = [
            {
              pageX: event.pageX,
              pageY: event.pageY
            }
          ];
          this.ontouchstart(event);
        }
      },
  
      onmousemove: function (event) {
        if (this.touchStart) {
          ;(event.originalEvent || event).touches = [
            {
              pageX: event.pageX,
              pageY: event.pageY
            }
          ];
          this.ontouchmove(event);
        }
      },
  
      onmouseup: function (event) {
        if (this.touchStart) {
          this.ontouchend(event);
          delete this.touchStart;
        }
      },
  
      onmouseout: function (event) {
        if (this.touchStart) {
          var target = event.target;
          var related = event.relatedTarget;
          if (!related || (related !== target && !$.contains(target, related))) {
            this.onmouseup(event);
          }
        }
      },
  
      ontouchstart: function (event) {
        if (this.options.stopTouchEventsPropagation) {
          this.stopPropagation(event);
        }
        // jQuery doesn't copy touch event properties by default,
        // so we have to access the originalEvent object:
        var touches = (event.originalEvent || event).touches[0];
        this.touchStart = {
          // Remember the initial touch coordinates:
          x: touches.pageX,
          y: touches.pageY,
          // Store the time to determine touch duration:
          time: Date.now()
        };
        // Helper variable to detect scroll movement:
        this.isScrolling = undefined;
        // Reset delta values:
        this.touchDelta = {};
      },
  
      ontouchmove: function (event) {
        if (this.options.stopTouchEventsPropagation) {
          this.stopPropagation(event);
        }
        // jQuery doesn't copy touch event properties by default,
        // so we have to access the originalEvent object:
        var touches = (event.originalEvent || event).touches[0];
        var scale = (event.originalEvent || event).scale;
        var index = this.index;
        var touchDeltaX;
        var indices;
        // Ensure this is a one touch swipe and not, e.g. a pinch:
          if (touches.length > 1 || (scale && scale !== 1)) {
          return;
          }
        if (this.options.disableScroll) {
          event.preventDefault();
        }
        // Measure change in x and y coordinates:
        this.touchDelta = {
          x: touches.pageX - this.touchStart.x,
          y: touches.pageY - this.touchStart.y
        };
        touchDeltaX = this.touchDelta.x;
        // Detect if this is a vertical scroll movement (run only once per touch):
        if (this.isScrolling === undefined) {
          this.isScrolling =
            this.isScrolling ||
            Math.abs(touchDeltaX) < Math.abs(this.touchDelta.y);
        }
        if (!this.isScrolling) {
          // Always prevent horizontal scroll:
          event.preventDefault();
          // Stop the slideshow:
          window.clearTimeout(this.timeout);
          if (this.options.continuous) {
            indices = [this.circle(index + 1), index, this.circle(index - 1)];
          } else {
            // Increase resistance if first slide and sliding left
            // or last slide and sliding right:
            this.touchDelta.x = touchDeltaX =
              touchDeltaX /
              ((!index && touchDeltaX > 0) ||
              (index === this.num - 1 && touchDeltaX < 0)
                ? Math.abs(touchDeltaX) / this.slideWidth + 1
                : 1);
            indices = [index];
            if (index) {
              indices.push(index - 1);
            }
            if (index < this.num - 1) {
              indices.unshift(index + 1);
            }
          }
          while (indices.length) {
            index = indices.pop();
            this.translateX(index, touchDeltaX + this.positions[index], 0);
          }
        } else {
          this.translateY(index, this.touchDelta.y + this.positions[index], 0);
        }
      },
  
      ontouchend: function (event) {
        if (this.options.stopTouchEventsPropagation) {
          this.stopPropagation(event);
        }
        var index = this.index;
        var speed = this.options.transitionSpeed;
        var slideWidth = this.slideWidth;
        var isShortDuration = Number(Date.now() - this.touchStart.time) < 250;
        // Determine if slide attempt triggers next/prev slide:
        var isValidSlide =
          (isShortDuration && Math.abs(this.touchDelta.x) > 20) ||
          Math.abs(this.touchDelta.x) > slideWidth / 2;
        // Determine if slide attempt is past start or end:
        var isPastBounds =
          (!index && this.touchDelta.x > 0) ||
          (index === this.num - 1 && this.touchDelta.x < 0);
        var isValidClose =
          !isValidSlide &&
          this.options.closeOnSwipeUpOrDown &&
          ((isShortDuration && Math.abs(this.touchDelta.y) > 20) ||
            Math.abs(this.touchDelta.y) > this.slideHeight / 2);
        var direction;
        var indexForward;
        var indexBackward;
        var distanceForward;
        var distanceBackward;
        if (this.options.continuous) {
          isPastBounds = false;
        }
        // Determine direction of swipe (true: right, false: left):
        direction = this.touchDelta.x < 0 ? -1 : 1;
        if (!this.isScrolling) {
          if (isValidSlide && !isPastBounds) {
            indexForward = index + direction;
            indexBackward = index - direction;
            distanceForward = slideWidth * direction;
            distanceBackward = -slideWidth * direction;
            if (this.options.continuous) {
              this.move(this.circle(indexForward), distanceForward, 0);
              this.move(this.circle(index - 2 * direction), distanceBackward, 0);
            } else if (indexForward >= 0 && indexForward < this.num) {
              this.move(indexForward, distanceForward, 0);
            }
            this.move(index, this.positions[index] + distanceForward, speed);
            this.move(
              this.circle(indexBackward),
              this.positions[this.circle(indexBackward)] + distanceForward,
              speed
            );
            index = this.circle(indexBackward);
            this.onslide(index);
          } else {
            // Move back into position
            if (this.options.continuous) {
              this.move(this.circle(index - 1), -slideWidth, speed);
              this.move(index, 0, speed);
              this.move(this.circle(index + 1), slideWidth, speed);
            } else {
              if (index) {
                this.move(index - 1, -slideWidth, speed);
              }
              this.move(index, 0, speed);
              if (index < this.num - 1) {
                this.move(index + 1, slideWidth, speed);
              }
            }
          }
        } else {
          if (isValidClose) {
            this.close();
          } else {
            // Move back into position
            this.translateY(index, 0, speed);
          }
        }
      },
  
      ontouchcancel: function (event) {
        if (this.touchStart) {
          this.ontouchend(event);
          delete this.touchStart;
        }
      },
  
      ontransitionend: function (event) {
        var slide = this.slides[this.index];
        if (!event || slide === event.target) {
          if (this.interval) {
            this.play();
          }
          this.setTimeout(this.options.onslideend, [this.index, slide]);
        }
      },
  
      oncomplete: function (event) {
        var target = event.target || event.srcElement;
        var parent = target && target.parentNode;
        var index;
        if (!target || !parent) {
          return;
        }
        index = this.getNodeIndex(parent);
        $(parent).removeClass(this.options.slideLoadingClass);
        if (event.type === 'error') {
          $(parent).addClass(this.options.slideErrorClass);
          this.elements[index] = 3; // Fail
        } else {
          this.elements[index] = 2; // Done
        }
        // Fix for IE7's lack of support for percentage max-height:
        if (target.clientHeight > this.container[0].clientHeight) {
          target.style.maxHeight = this.container[0].clientHeight;
        }
        if (this.interval && this.slides[this.index] === parent) {
          this.play();
        }
        this.setTimeout(this.options.onslidecomplete, [index, parent]);
      },
  
      onload: function (event) {
        this.oncomplete(event);
      },
  
      onerror: function (event) {
        this.oncomplete(event);
      },
  
      onkeydown: function (event) {
        switch (event.which || event.keyCode) {
          case 13: // Return
            if (this.options.toggleControlsOnReturn) {
              this.preventDefault(event);
              this.toggleControls();
            }
            break;
          case 27: // Esc
            if (this.options.closeOnEscape) {
              this.close();
              // prevent Esc from closing other things
              event.stopImmediatePropagation();
            }
            break;
          case 32: // Space
            if (this.options.toggleSlideshowOnSpace) {
              this.preventDefault(event);
              this.toggleSlideshow();
            }
            break;
          case 37: // Left
            if (this.options.enableKeyboardNavigation) {
              this.preventDefault(event);
              this.prev();
            }
            break;
          case 39: // Right
            if (this.options.enableKeyboardNavigation) {
              this.preventDefault(event);
              this.next();
            }
            break;
        }
      },
  
      handleClick: function (event) {
        var options = this.options;
        var target = event.target || event.srcElement;
        var parent = target.parentNode;
  
        /**
         * Checks if the target from the close has the given class
         *
         * @param {string} className Class name
         * @returns {boolean} Returns true if the target has the class name
         */
        function isTarget(className) {
          return $(target).hasClass(className) || $(parent).hasClass(className);
        }
        if (isTarget(options.toggleClass)) {
          // Click on "toggle" control
          this.preventDefault(event);
          this.toggleControls();
        } else if (isTarget(options.prevClass)) {
          // Click on "prev" control
          this.preventDefault(event);
          this.prev();
        } else if (isTarget(options.nextClass)) {
          // Click on "next" control
          this.preventDefault(event);
          this.next();
        } else if (isTarget(options.closeClass)) {
          // Click on "close" control
          this.preventDefault(event);
          this.close();
        } else if (isTarget(options.playPauseClass)) {
          // Click on "play-pause" control
          this.preventDefault(event);
          this.toggleSlideshow();
        } else if (parent === this.slidesContainer[0]) {
          // Click on slide background
          if (options.closeOnSlideClick) {
            this.preventDefault(event);
            this.close();
          } else if (options.toggleControlsOnSlideClick) {
            this.preventDefault(event);
            this.toggleControls();
          }
        } else if (
          parent.parentNode &&
          parent.parentNode === this.slidesContainer[0]
        ) {
          // Click on displayed element
          if (options.toggleControlsOnSlideClick) {
            this.preventDefault(event);
            this.toggleControls();
          }
        }
      },
  
      onclick: function (event) {
        if (
          this.options.emulateTouchEvents &&
          this.touchDelta &&
          (Math.abs(this.touchDelta.x) > 20 || Math.abs(this.touchDelta.y) > 20)
        ) {
          delete this.touchDelta;
          return;
        }
        return this.handleClick(event);
      },
  
      updateEdgeClasses: function (index) {
        if (!index) {
          this.container.addClass(this.options.leftEdgeClass);
        } else {
          this.container.removeClass(this.options.leftEdgeClass);
        }
        if (index === this.num - 1) {
          this.container.addClass(this.options.rightEdgeClass);
        } else {
          this.container.removeClass(this.options.rightEdgeClass);
        }
      },
  
      handleSlide: function (index) {
        if (!this.options.continuous) {
          this.updateEdgeClasses(index);
        }
        this.loadElements(index);
        if (this.options.unloadElements) {
          this.unloadElements(index);
        }
        this.setTitle(index);
      },
  
      onslide: function (index) {
        this.index = index;
        this.handleSlide(index);
        this.setTimeout(this.options.onslide, [index, this.slides[index]]);
      },
  
      setTitle: function (index) {
        var firstChild = this.slides[index].firstChild;
        var text = firstChild.title || firstChild.alt;
        var titleElement = this.titleElement;
        if (titleElement.length) {
          this.titleElement.empty();
          if (text) {
            titleElement[0].appendChild(document.createTextNode(text));
          }
        }
      },
  
      setTimeout: function (func, args, wait) {
        var that = this;
        return (
          func &&
          window.setTimeout(function () {
            func.apply(that, args || []);
          }, wait || 0)
        );
      },
  
      imageFactory: function (obj, callback) {
        var that = this;
        var img = this.imagePrototype.cloneNode(false);
        var url = obj;
        var backgroundSize = this.options.stretchImages;
        var called;
        var element;
        var title;
        var altText;
  
        /**
         * Wraps the callback function for the load/error event
         *
         * @param {event} event load/error event
         * @returns {number} timeout ID
         */
        function callbackWrapper(event) {
          if (!called) {
            event = {
              type: event.type,
              target: element
            };
            if (!element.parentNode) {
              // Fix for IE7 firing the load event for
              // cached images before the element could
              // be added to the DOM:
              return that.setTimeout(callbackWrapper, [event]);
            }
            called = true;
            $(img).off('load error', callbackWrapper);
            if (backgroundSize) {
              if (event.type === 'load') {
                element.style.background = 'url("' + url + '") center no-repeat';
                element.style.backgroundSize = backgroundSize;
              }
            }
            callback(event);
          }
        }
        if (typeof url !== 'string') {
          url = this.getItemProperty(obj, this.options.urlProperty);
          title = this.getItemProperty(obj, this.options.titleProperty);
          altText =
            this.getItemProperty(obj, this.options.altTextProperty) || title;
        }
        if (backgroundSize === true) {
          backgroundSize = 'contain';
        }
        backgroundSize =
          this.support.backgroundSize &&
          this.support.backgroundSize[backgroundSize] &&
          backgroundSize;
        if (backgroundSize) {
          element = this.elementPrototype.cloneNode(false);
        } else {
          element = img;
          img.draggable = false;
        }
        if (title) {
          element.title = title;
        }
        if (altText) {
          element.alt = altText;
        }
        $(img).on('load error', callbackWrapper);
        img.src = url;
        return element;
      },
  
      createElement: function (obj, callback) {
        var type = obj && this.getItemProperty(obj, this.options.typeProperty);
        var factory =
          (type && this[type.split('/')[0] + 'Factory']) || this.imageFactory;
        var element = obj && factory.call(this, obj, callback);
        var srcset = this.getItemProperty(obj, this.options.srcsetProperty);
        if (!element) {
          element = this.elementPrototype.cloneNode(false);
          this.setTimeout(callback, [
            {
              type: 'error',
              target: element
            }
          ]);
        }
        if (srcset) {
          element.setAttribute('srcset', srcset);
        }
        $(element).addClass(this.options.slideContentClass);
        return element;
      },
  
      loadElement: function (index) {
        if (!this.elements[index]) {
          if (this.slides[index].firstChild) {
            this.elements[index] = $(this.slides[index]).hasClass(
              this.options.slideErrorClass
            )
              ? 3
              : 2;
          } else {
            this.elements[index] = 1; // Loading
            $(this.slides[index]).addClass(this.options.slideLoadingClass);
            this.slides[index].appendChild(
              this.createElement(this.list[index], this.proxyListener)
            );
          }
        }
      },
  
      loadElements: function (index) {
        var limit = Math.min(this.num, this.options.preloadRange * 2 + 1);
        var j = index;
        var i;
        for (i = 0; i < limit; i += 1) {
          // First load the current slide element (0),
          // then the next one (+1),
          // then the previous one (-2),
          // then the next after next (+2), etc.:
          j += i * (i % 2 === 0 ? -1 : 1);
          // Connect the ends of the list to load slide elements for
          // continuous navigation:
          j = this.circle(j);
          this.loadElement(j);
        }
      },
  
      unloadElements: function (index) {
        var i, diff;
        for (i in this.elements) {
          if (Object.prototype.hasOwnProperty.call(this.elements, i)) {
            diff = Math.abs(index - i);
            if (
              diff > this.options.preloadRange &&
              diff + this.options.preloadRange < this.num
            ) {
              this.unloadSlide(i);
              delete this.elements[i];
            }
          }
        }
      },
  
      addSlide: function (index) {
        var slide = this.slidePrototype.cloneNode(false);
        slide.setAttribute('data-index', index);
        this.slidesContainer[0].appendChild(slide);
        this.slides.push(slide);
      },
  
      positionSlide: function (index) {
        var slide = this.slides[index];
        slide.style.width = this.slideWidth + 'px';
        if (this.support.transform) {
          slide.style.left = index * -this.slideWidth + 'px';
          this.move(
            index,
            this.index > index
              ? -this.slideWidth
              : this.index < index
              ? this.slideWidth
              : 0,
            0
          );
        }
      },
  
      initSlides: function (reload) {
        var clearSlides, i;
        if (!reload) {
          this.positions = [];
          this.positions.length = this.num;
          this.elements = {};
          this.imagePrototype = document.createElement('img');
          this.elementPrototype = document.createElement('div');
          this.slidePrototype = document.createElement('div');
          $(this.slidePrototype).addClass(this.options.slideClass);
          this.slides = this.slidesContainer[0].children;
          clearSlides =
            this.options.clearSlides || this.slides.length !== this.num;
        }
        this.slideWidth = this.container[0].clientWidth;
        this.slideHeight = this.container[0].clientHeight;
        this.slidesContainer[0].style.width = this.num * this.slideWidth + 'px';
        if (clearSlides) {
          this.resetSlides();
        }
        for (i = 0; i < this.num; i += 1) {
          if (clearSlides) {
            this.addSlide(i);
          }
          this.positionSlide(i);
        }
        // Reposition the slides before and after the given index:
        if (this.options.continuous && this.support.transform) {
          this.move(this.circle(this.index - 1), -this.slideWidth, 0);
          this.move(this.circle(this.index + 1), this.slideWidth, 0);
        }
        if (!this.support.transform) {
          this.slidesContainer[0].style.left =
            this.index * -this.slideWidth + 'px';
        }
      },
  
      unloadSlide: function (index) {
        var slide, firstChild;
        slide = this.slides[index];
        firstChild = slide.firstChild;
        if (firstChild !== null) {
          slide.removeChild(firstChild);
        }
      },
  
      unloadAllSlides: function () {
        var i, len;
        for (i = 0, len = this.slides.length; i < len; i++) {
          this.unloadSlide(i);
        }
      },
  
      toggleControls: function () {
        var controlsClass = this.options.controlsClass;
        if (this.container.hasClass(controlsClass)) {
          this.container.removeClass(controlsClass);
        } else {
          this.container.addClass(controlsClass);
        }
      },
  
      toggleSlideshow: function () {
        if (!this.interval) {
          this.play();
        } else {
          this.pause();
        }
      },
  
      getNodeIndex: function (element) {
        return parseInt(element.getAttribute('data-index'), 10);
      },
  
      getNestedProperty: function (obj, property) {
        property.replace(
          // Matches native JavaScript notation in a String,
          // e.g. '["doubleQuoteProp"].dotProp[2]'
          // eslint-disable-next-line no-useless-escape
          /\[(?:'([^']+)'|"([^"]+)"|(\d+))\]|(?:(?:^|\.)([^\.\[]+))/g,
          function (str, singleQuoteProp, doubleQuoteProp, arrayIndex, dotProp) {
            var prop =
              dotProp ||
              singleQuoteProp ||
              doubleQuoteProp ||
              (arrayIndex && parseInt(arrayIndex, 10));
            if (str && obj) {
              obj = obj[prop];
            }
          }
        );
        return obj;
      },
  
      getDataProperty: function (obj, property) {
        var key;
        var prop;
        if (obj.dataset) {
          key = property.replace(/-([a-z])/g, function (_, b) {
            return b.toUpperCase();
          });
          prop = obj.dataset[key];
        } else if (obj.getAttribute) {
          prop = obj.getAttribute(
            'data-' + property.replace(/([A-Z])/g, '-$1').toLowerCase()
          );
        }
        if (typeof prop === 'string') {
          // eslint-disable-next-line no-useless-escape
          if (
            /^(true|false|null|-?\d+(\.\d+)?|\{[\s\S]*\}|\[[\s\S]*\])$/.test(prop)
          ) {
            try {
              return $.parseJSON(prop);
            } catch (ignore) {
              // ignore JSON parsing errors
            }
          }
          return prop;
        }
      },
  
      getItemProperty: function (obj, property) {
        var prop = this.getDataProperty(obj, property);
        if (prop === undefined) {
          prop = obj[property];
        }
        if (prop === undefined) {
          prop = this.getNestedProperty(obj, property);
        }
        return prop;
      },
  
      initStartIndex: function () {
        var index = this.options.index;
        var urlProperty = this.options.urlProperty;
        var i;
        // Check if the index is given as a list object:
        if (index && typeof index !== 'number') {
          for (i = 0; i < this.num; i += 1) {
            if (
              this.list[i] === index ||
              this.getItemProperty(this.list[i], urlProperty) ===
                this.getItemProperty(index, urlProperty)
            ) {
              index = i;
              break;
            }
          }
        }
        // Make sure the index is in the list range:
        this.index = this.circle(parseInt(index, 10) || 0);
      },
  
      initEventListeners: function () {
        var that = this;
        var slidesContainer = this.slidesContainer;
  
        /**
         * Proxy listener
         *
         * @param {event} event original event
         */
        function proxyListener(event) {
          var type =
            that.support.transition && that.support.transition.end === event.type
              ? 'transitionend'
              : event.type;
          that['on' + type](event);
        }
        $(window).on('resize', proxyListener);
        $(document.body).on('keydown', proxyListener);
        this.container.on('click', proxyListener);
        if (this.support.touch) {
          slidesContainer.on(
            'touchstart touchmove touchend touchcancel',
            proxyListener
          );
        } else if (this.options.emulateTouchEvents && this.support.transition) {
          slidesContainer.on(
            'mousedown mousemove mouseup mouseout',
            proxyListener
          );
        }
        if (this.support.transition) {
          slidesContainer.on(this.support.transition.end, proxyListener);
        }
        this.proxyListener = proxyListener;
      },
  
      destroyEventListeners: function () {
        var slidesContainer = this.slidesContainer;
        var proxyListener = this.proxyListener;
        $(window).off('resize', proxyListener);
        $(document.body).off('keydown', proxyListener);
        this.container.off('click', proxyListener);
        if (this.support.touch) {
          slidesContainer.off(
            'touchstart touchmove touchend touchcancel',
            proxyListener
          );
        } else if (this.options.emulateTouchEvents && this.support.transition) {
          slidesContainer.off(
            'mousedown mousemove mouseup mouseout',
            proxyListener
          );
        }
        if (this.support.transition) {
          slidesContainer.off(this.support.transition.end, proxyListener);
        }
      },
  
      handleOpen: function () {
        if (this.options.onopened) {
          this.options.onopened.call(this);
        }
      },
  
      initWidget: function () {
        var that = this;
  
        /**
         * Open handler
         *
         * @param {event} event Gallery open event
         */
        function openHandler(event) {
          if (event.target === that.container[0]) {
            that.container.off(that.support.transition.end, openHandler);
            that.handleOpen();
          }
        }
        this.container = $(this.options.container);
        if (!this.container.length) {
          this.console.log(
            'blueimp Gallery: Widget container not found.',
            this.options.container
          );
          return false;
        }
        this.slidesContainer = this.container
          .find(this.options.slidesContainer)
          .first();
        if (!this.slidesContainer.length) {
          this.console.log(
            'blueimp Gallery: Slides container not found.',
            this.options.slidesContainer
          );
          return false;
        }
        this.titleElement = this.container.find(this.options.titleElement).first();
        if (this.num === 1) {
          this.container.addClass(this.options.singleClass);
        }
        if (this.options.onopen) {
          this.options.onopen.call(this);
        }
        if (this.support.transition && this.options.displayTransition) {
          this.container.on(this.support.transition.end, openHandler);
        } else {
          this.handleOpen();
        }
        if (this.options.hidePageScrollbars) {
          // Hide the page scrollbars:
          this.bodyOverflowStyle = document.body.style.overflow;
          document.body.style.overflow = 'hidden';
        }
        this.container[0].style.display = 'block';
        this.initSlides();
        this.container.addClass(this.options.displayClass);
      },
  
      initOptions: function (options) {
        // Create a copy of the prototype options:
        this.options = $.extend({}, this.options);
        // Check if carousel mode is enabled:
        if (
          (options && options.carousel) ||
          (this.options.carousel && (!options || options.carousel !== false))
        ) {
          $.extend(this.options, this.carouselOptions);
        }
        // Override any given options:
        $.extend(this.options, options);
        if (this.num < 3) {
          // 1 or 2 slides cannot be displayed continuous,
          // remember the original option by setting to null instead of false:
          this.options.continuous = this.options.continuous ? null : false;
        }
        if (!this.support.transition) {
          this.options.emulateTouchEvents = false;
        }
        if (this.options.event) {
          this.preventDefault(this.options.event);
        }
      }
    });
  
    return Gallery;
  });
  