/*!
Waypoints - 4.0.1
Copyright © 2011-2016 Caleb Troughton
Licensed under the MIT license.
https://github.com/imakewebthings/waypoints/blob/master/licenses.txt
*/
(function() {
  'use strict'

  var keyCounter = 0
  var allWaypoints = {}

  /* http://imakewebthings.com/waypoints/api/waypoint */
  function Waypoint(options) {
    if (!options) {
      throw new Error('No options passed to Waypoint constructor')
    }
    if (!options.element) {
      throw new Error('No element option passed to Waypoint constructor')
    }
    if (!options.handler) {
      throw new Error('No handler option passed to Waypoint constructor')
    }

    this.key = 'waypoint-' + keyCounter
    this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
    this.element = this.options.element
    this.adapter = new Waypoint.Adapter(this.element)
    this.callback = options.handler
    this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
    this.enabled = this.options.enabled
    this.triggerPoint = null
    this.group = Waypoint.Group.findOrCreate({
      name: this.options.group,
      axis: this.axis
    })
    this.context = Waypoint.Context.findOrCreateByElement(this.options.context)

    if (Waypoint.offsetAliases[this.options.offset]) {
      this.options.offset = Waypoint.offsetAliases[this.options.offset]
    }
    this.group.add(this)
    this.context.add(this)
    allWaypoints[this.key] = this
    keyCounter += 1
  }

  /* Private */
  Waypoint.prototype.queueTrigger = function(direction) {
    this.group.queueTrigger(this, direction)
  }

  /* Private */
  Waypoint.prototype.trigger = function(args) {
    if (!this.enabled) {
      return
    }
    if (this.callback) {
      this.callback.apply(this, args)
    }
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/destroy */
  Waypoint.prototype.destroy = function() {
    this.context.remove(this)
    this.group.remove(this)
    delete allWaypoints[this.key]
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/disable */
  Waypoint.prototype.disable = function() {
    this.enabled = false
    return this
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/enable */
  Waypoint.prototype.enable = function() {
    this.context.refresh()
    this.enabled = true
    return this
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/next */
  Waypoint.prototype.next = function() {
    return this.group.next(this)
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/previous */
  Waypoint.prototype.previous = function() {
    return this.group.previous(this)
  }

  /* Private */
  Waypoint.invokeAll = function(method) {
    var allWaypointsArray = []
    for (var waypointKey in allWaypoints) {
      allWaypointsArray.push(allWaypoints[waypointKey])
    }
    for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
      allWaypointsArray[i][method]()
    }
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/destroy-all */
  Waypoint.destroyAll = function() {
    Waypoint.invokeAll('destroy')
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/disable-all */
  Waypoint.disableAll = function() {
    Waypoint.invokeAll('disable')
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/enable-all */
  Waypoint.enableAll = function() {
    Waypoint.Context.refreshAll()
    for (var waypointKey in allWaypoints) {
      allWaypoints[waypointKey].enabled = true
    }
    return this
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/refresh-all */
  Waypoint.refreshAll = function() {
    Waypoint.Context.refreshAll()
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/viewport-height */
  Waypoint.viewportHeight = function() {
    return window.innerHeight || document.documentElement.clientHeight
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/viewport-width */
  Waypoint.viewportWidth = function() {
    return document.documentElement.clientWidth
  }

  Waypoint.adapters = []

  Waypoint.defaults = {
    context: window,
    continuous: true,
    enabled: true,
    group: 'default',
    horizontal: false,
    offset: 0
  }

  Waypoint.offsetAliases = {
    'bottom-in-view': function() {
      return this.context.innerHeight() - this.adapter.outerHeight()
    },
    'right-in-view': function() {
      return this.context.innerWidth() - this.adapter.outerWidth()
    }
  }

  window.Waypoint = Waypoint
}())
;(function() {
  'use strict'

  function requestAnimationFrameShim(callback) {
    window.setTimeout(callback, 1000 / 60)
  }

  var keyCounter = 0
  var contexts = {}
  var Waypoint = window.Waypoint
  var oldWindowLoad = window.onload

  /* http://imakewebthings.com/waypoints/api/context */
  function Context(element) {
    this.element = element
    this.Adapter = Waypoint.Adapter
    this.adapter = new this.Adapter(element)
    this.key = 'waypoint-context-' + keyCounter
    this.didScroll = false
    this.didResize = false
    this.oldScroll = {
      x: this.adapter.scrollLeft(),
      y: this.adapter.scrollTop()
    }
    this.waypoints = {
      vertical: {},
      horizontal: {}
    }

    element.waypointContextKey = this.key
    contexts[element.waypointContextKey] = this
    keyCounter += 1
    if (!Waypoint.windowContext) {
      Waypoint.windowContext = true
      Waypoint.windowContext = new Context(window)
    }

    this.createThrottledScrollHandler()
    this.createThrottledResizeHandler()
  }

  /* Private */
  Context.prototype.add = function(waypoint) {
    var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
    this.waypoints[axis][waypoint.key] = waypoint
    this.refresh()
  }

  /* Private */
  Context.prototype.checkEmpty = function() {
    var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
    var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
    var isWindow = this.element == this.element.window
    if (horizontalEmpty && verticalEmpty && !isWindow) {
      this.adapter.off('.waypoints')
      delete contexts[this.key]
    }
  }

  /* Private */
  Context.prototype.createThrottledResizeHandler = function() {
    var self = this

    function resizeHandler() {
      self.handleResize()
      self.didResize = false
    }

    this.adapter.on('resize.waypoints', function() {
      if (!self.didResize) {
        self.didResize = true
        Waypoint.requestAnimationFrame(resizeHandler)
      }
    })
  }

  /* Private */
  Context.prototype.createThrottledScrollHandler = function() {
    var self = this
    function scrollHandler() {
      self.handleScroll()
      self.didScroll = false
    }

    this.adapter.on('scroll.waypoints', function() {
      if (!self.didScroll || Waypoint.isTouch) {
        self.didScroll = true
        Waypoint.requestAnimationFrame(scrollHandler)
      }
    })
  }

  /* Private */
  Context.prototype.handleResize = function() {
    Waypoint.Context.refreshAll()
  }

  /* Private */
  Context.prototype.handleScroll = function() {
    var triggeredGroups = {}
    var axes = {
      horizontal: {
        newScroll: this.adapter.scrollLeft(),
        oldScroll: this.oldScroll.x,
        forward: 'right',
        backward: 'left'
      },
      vertical: {
        newScroll: this.adapter.scrollTop(),
        oldScroll: this.oldScroll.y,
        forward: 'down',
        backward: 'up'
      }
    }

    for (var axisKey in axes) {
      var axis = axes[axisKey]
      var isForward = axis.newScroll > axis.oldScroll
      var direction = isForward ? axis.forward : axis.backward

      for (var waypointKey in this.waypoints[axisKey]) {
        var waypoint = this.waypoints[axisKey][waypointKey]
        if (waypoint.triggerPoint === null) {
          continue
        }
        var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
        var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
        var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
        var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
        if (crossedForward || crossedBackward) {
          waypoint.queueTrigger(direction)
          triggeredGroups[waypoint.group.id] = waypoint.group
        }
      }
    }

    for (var groupKey in triggeredGroups) {
      triggeredGroups[groupKey].flushTriggers()
    }

    this.oldScroll = {
      x: axes.horizontal.newScroll,
      y: axes.vertical.newScroll
    }
  }

  /* Private */
  Context.prototype.innerHeight = function() {
    /*eslint-disable eqeqeq */
    if (this.element == this.element.window) {
      return Waypoint.viewportHeight()
    }
    /*eslint-enable eqeqeq */
    return this.adapter.innerHeight()
  }

  /* Private */
  Context.prototype.remove = function(waypoint) {
    delete this.waypoints[waypoint.axis][waypoint.key]
    this.checkEmpty()
  }

  /* Private */
  Context.prototype.innerWidth = function() {
    /*eslint-disable eqeqeq */
    if (this.element == this.element.window) {
      return Waypoint.viewportWidth()
    }
    /*eslint-enable eqeqeq */
    return this.adapter.innerWidth()
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/context-destroy */
  Context.prototype.destroy = function() {
    var allWaypoints = []
    for (var axis in this.waypoints) {
      for (var waypointKey in this.waypoints[axis]) {
        allWaypoints.push(this.waypoints[axis][waypointKey])
      }
    }
    for (var i = 0, end = allWaypoints.length; i < end; i++) {
      allWaypoints[i].destroy()
    }
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/context-refresh */
  Context.prototype.refresh = function() {
    /*eslint-disable eqeqeq */
    var isWindow = this.element == this.element.window
    /*eslint-enable eqeqeq */
    var contextOffset = isWindow ? undefined : this.adapter.offset()
    var triggeredGroups = {}
    var axes

    this.handleScroll()
    axes = {
      horizontal: {
        contextOffset: isWindow ? 0 : contextOffset.left,
        contextScroll: isWindow ? 0 : this.oldScroll.x,
        contextDimension: this.innerWidth(),
        oldScroll: this.oldScroll.x,
        forward: 'right',
        backward: 'left',
        offsetProp: 'left'
      },
      vertical: {
        contextOffset: isWindow ? 0 : contextOffset.top,
        contextScroll: isWindow ? 0 : this.oldScroll.y,
        contextDimension: this.innerHeight(),
        oldScroll: this.oldScroll.y,
        forward: 'down',
        backward: 'up',
        offsetProp: 'top'
      }
    }

    for (var axisKey in axes) {
      var axis = axes[axisKey]
      for (var waypointKey in this.waypoints[axisKey]) {
        var waypoint = this.waypoints[axisKey][waypointKey]
        var adjustment = waypoint.options.offset
        var oldTriggerPoint = waypoint.triggerPoint
        var elementOffset = 0
        var freshWaypoint = oldTriggerPoint == null
        var contextModifier, wasBeforeScroll, nowAfterScroll
        var triggeredBackward, triggeredForward

        if (waypoint.element !== waypoint.element.window) {
          elementOffset = waypoint.adapter.offset()[axis.offsetProp]
        }

        if (typeof adjustment === 'function') {
          adjustment = adjustment.apply(waypoint)
        }
        else if (typeof adjustment === 'string') {
          adjustment = parseFloat(adjustment)
          if (waypoint.options.offset.indexOf('%') > - 1) {
            adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
          }
        }

        contextModifier = axis.contextScroll - axis.contextOffset
        waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment)
        wasBeforeScroll = oldTriggerPoint < axis.oldScroll
        nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
        triggeredBackward = wasBeforeScroll && nowAfterScroll
        triggeredForward = !wasBeforeScroll && !nowAfterScroll

        if (!freshWaypoint && triggeredBackward) {
          waypoint.queueTrigger(axis.backward)
          triggeredGroups[waypoint.group.id] = waypoint.group
        }
        else if (!freshWaypoint && triggeredForward) {
          waypoint.queueTrigger(axis.forward)
          triggeredGroups[waypoint.group.id] = waypoint.group
        }
        else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
          waypoint.queueTrigger(axis.forward)
          triggeredGroups[waypoint.group.id] = waypoint.group
        }
      }
    }

    Waypoint.requestAnimationFrame(function() {
      for (var groupKey in triggeredGroups) {
        triggeredGroups[groupKey].flushTriggers()
      }
    })

    return this
  }

  /* Private */
  Context.findOrCreateByElement = function(element) {
    return Context.findByElement(element) || new Context(element)
  }

  /* Private */
  Context.refreshAll = function() {
    for (var contextId in contexts) {
      contexts[contextId].refresh()
    }
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/context-find-by-element */
  Context.findByElement = function(element) {
    return contexts[element.waypointContextKey]
  }

  window.onload = function() {
    if (oldWindowLoad) {
      oldWindowLoad()
    }
    Context.refreshAll()
  }


  Waypoint.requestAnimationFrame = function(callback) {
    var requestFn = window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        requestAnimationFrameShim
    requestFn.call(window, callback)
  }
  Waypoint.Context = Context
}())
;(function() {
  'use strict'

  function byTriggerPoint(a, b) {
    return a.triggerPoint - b.triggerPoint
  }

  function byReverseTriggerPoint(a, b) {
    return b.triggerPoint - a.triggerPoint
  }

  var groups = {
    vertical: {},
    horizontal: {}
  }
  var Waypoint = window.Waypoint

  /* http://imakewebthings.com/waypoints/api/group */
  function Group(options) {
    this.name = options.name
    this.axis = options.axis
    this.id = this.name + '-' + this.axis
    this.waypoints = []
    this.clearTriggerQueues()
    groups[this.axis][this.name] = this
  }

  /* Private */
  Group.prototype.add = function(waypoint) {
    this.waypoints.push(waypoint)
  }

  /* Private */
  Group.prototype.clearTriggerQueues = function() {
    this.triggerQueues = {
      up: [],
      down: [],
      left: [],
      right: []
    }
  }

  /* Private */
  Group.prototype.flushTriggers = function() {
    for (var direction in this.triggerQueues) {
      var waypoints = this.triggerQueues[direction]
      var reverse = direction === 'up' || direction === 'left'
      waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
      for (var i = 0, end = waypoints.length; i < end; i += 1) {
        var waypoint = waypoints[i]
        if (waypoint.options.continuous || i === waypoints.length - 1) {
          waypoint.trigger([direction])
        }
      }
    }
    this.clearTriggerQueues()
  }

  /* Private */
  Group.prototype.next = function(waypoint) {
    this.waypoints.sort(byTriggerPoint)
    var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
    var isLast = index === this.waypoints.length - 1
    return isLast ? null : this.waypoints[index + 1]
  }

  /* Private */
  Group.prototype.previous = function(waypoint) {
    this.waypoints.sort(byTriggerPoint)
    var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
    return index ? this.waypoints[index - 1] : null
  }

  /* Private */
  Group.prototype.queueTrigger = function(waypoint, direction) {
    this.triggerQueues[direction].push(waypoint)
  }

  /* Private */
  Group.prototype.remove = function(waypoint) {
    var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
    if (index > -1) {
      this.waypoints.splice(index, 1)
    }
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/first */
  Group.prototype.first = function() {
    return this.waypoints[0]
  }

  /* Public */
  /* http://imakewebthings.com/waypoints/api/last */
  Group.prototype.last = function() {
    return this.waypoints[this.waypoints.length - 1]
  }

  /* Private */
  Group.findOrCreate = function(options) {
    return groups[options.axis][options.name] || new Group(options)
  }

  Waypoint.Group = Group
}())
;(function() {
  'use strict'

  var $ = window.jQuery
  var Waypoint = window.Waypoint

  function JQueryAdapter(element) {
    this.$element = $(element)
  }

  $.each([
    'innerHeight',
    'innerWidth',
    'off',
    'offset',
    'on',
    'outerHeight',
    'outerWidth',
    'scrollLeft',
    'scrollTop'
  ], function(i, method) {
    JQueryAdapter.prototype[method] = function() {
      var args = Array.prototype.slice.call(arguments)
      return this.$element[method].apply(this.$element, args)
    }
  })

  $.each([
    'extend',
    'inArray',
    'isEmptyObject'
  ], function(i, method) {
    JQueryAdapter[method] = $[method]
  })

  Waypoint.adapters.push({
    name: 'jquery',
    Adapter: JQueryAdapter
  })
  Waypoint.Adapter = JQueryAdapter
}())
;(function() {
  'use strict'

  var Waypoint = window.Waypoint

  function createExtension(framework) {
    return function() {
      var waypoints = []
      var overrides = arguments[0]

      if (typeof arguments[0] === "function") {
        overrides = framework.extend({}, arguments[1])
        overrides.handler = arguments[0]
      }

      this.each(function() {
        var options = framework.extend({}, overrides, {
          element: this
        })
        if (typeof options.context === 'string') {
          options.context = framework(this).closest(options.context)[0]
        }
        waypoints.push(new Waypoint(options))
      })

      return waypoints
    }
  }

  if (window.jQuery) {
    window.jQuery.fn.waypoint = createExtension(window.jQuery)
  }
  if (window.Zepto) {
    window.Zepto.fn.waypoint = createExtension(window.Zepto)
  }
}())
;
/**
 * vivus - JavaScript library to make drawing animation on SVG
 * @version v0.4.0
 * @link https://github.com/maxwellito/vivus
 * @license MIT
 */

'use strict';

(function (window, document) {

  'use strict';

/**
 * Pathformer
 * Beta version
 *
 * Take any SVG version 1.1 and transform
 * child elements to 'path' elements
 *
 * This code is purely forked from
 * https://github.com/Waest/SVGPathConverter
 */

/**
 * Class constructor
 *
 * @param {DOM|String} element Dom element of the SVG or id of it
 */
function Pathformer(element) {
  // Test params
  if (typeof element === 'undefined') {
    throw new Error('Pathformer [constructor]: "element" parameter is required');
  }

  // Set the element
  if (element.constructor === String) {
    element = document.getElementById(element);
    if (!element) {
      throw new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID');
    }
  }
  if (element.constructor instanceof window.SVGElement || /^svg$/i.test(element.nodeName)) {
    this.el = element;
  } else {
    throw new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement');
  }

  // Start
  this.scan(element);
}

/**
 * List of tags which can be transformed
 * to path elements
 *
 * @type {Array}
 */
Pathformer.prototype.TYPES = ['line', 'ellipse', 'circle', 'polygon', 'polyline', 'rect'];

/**
 * List of attribute names which contain
 * data. This array list them to check if
 * they contain bad values, like percentage.
 *
 * @type {Array}
 */
Pathformer.prototype.ATTR_WATCH = ['cx', 'cy', 'points', 'r', 'rx', 'ry', 'x', 'x1', 'x2', 'y', 'y1', 'y2'];

/**
 * Finds the elements compatible for transform
 * and apply the liked method
 *
 * @param  {object} options Object from the constructor
 */
Pathformer.prototype.scan = function (svg) {
  var fn, element, pathData, pathDom,
      elements = svg.querySelectorAll(this.TYPES.join(','));

  for (var i = 0; i < elements.length; i++) {
    element = elements[i];
    fn = this[element.tagName.toLowerCase() + 'ToPath'];
    pathData = fn(this.parseAttr(element.attributes));
    pathDom = this.pathMaker(element, pathData);
    element.parentNode.replaceChild(pathDom, element);
  }
};


/**
 * Read `line` element to extract and transform
 * data, to make it ready for a `path` object.
 *
 * @param  {DOMelement} element Line element to transform
 * @return {object}             Data for a `path` element
 */
Pathformer.prototype.lineToPath = function (element) {
  var newElement = {},
      x1 = element.x1 || 0,
      y1 = element.y1 || 0,
      x2 = element.x2 || 0,
      y2 = element.y2 || 0;

  newElement.d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
  return newElement;
};

/**
 * Read `rect` element to extract and transform
 * data, to make it ready for a `path` object.
 * The radius-border is not taken in charge yet.
 * (your help is more than welcomed)
 *
 * @param  {DOMelement} element Rect element to transform
 * @return {object}             Data for a `path` element
 */
Pathformer.prototype.rectToPath = function (element) {
  var newElement = {},
      x      = parseFloat(element.x)      || 0,
      y      = parseFloat(element.y)      || 0,
      width  = parseFloat(element.width)  || 0,
      height = parseFloat(element.height) || 0;

  newElement.d  = 'M' + x + ' ' + y + ' ';
  newElement.d += 'L' + (x + width) + ' ' + y + ' ';
  newElement.d += 'L' + (x + width) + ' ' + (y + height) + ' ';
  newElement.d += 'L' + x + ' ' + (y + height) + ' Z';
  return newElement;
};

/**
 * Read `polyline` element to extract and transform
 * data, to make it ready for a `path` object.
 *
 * @param  {DOMelement} element Polyline element to transform
 * @return {object}             Data for a `path` element
 */
Pathformer.prototype.polylineToPath = function (element) {
  var newElement = {},
      points = element.points.trim().split(' '),
      i, path;

  // Reformatting if points are defined without commas
  if (element.points.indexOf(',') === -1) {
    var formattedPoints = [];
    for (i = 0; i < points.length; i+=2) {
      formattedPoints.push(points[i] + ',' + points[i+1]);
    }
    points = formattedPoints;
  }

  // Generate the path.d value
  path = 'M' + points[0];
  for(i = 1; i < points.length; i++) {
    if (points[i].indexOf(',') !== -1) {
      path += 'L' + points[i];
    }
  }
  newElement.d = path;
  return newElement;
};

/**
 * Read `polygon` element to extract and transform
 * data, to make it ready for a `path` object.
 * This method rely on polylineToPath, because the
 * logic is similar. The path created is just closed,
 * so it needs an 'Z' at the end.
 *
 * @param  {DOMelement} element Polygon element to transform
 * @return {object}             Data for a `path` element
 */
Pathformer.prototype.polygonToPath = function (element) {
  var newElement = Pathformer.prototype.polylineToPath(element);

  newElement.d += 'Z';
  return newElement;
};

/**
 * Read `ellipse` element to extract and transform
 * data, to make it ready for a `path` object.
 *
 * @param  {DOMelement} element ellipse element to transform
 * @return {object}             Data for a `path` element
 */
Pathformer.prototype.ellipseToPath = function (element) {
  var newElement = {},
      rx = parseFloat(element.rx) || 0,
      ry = parseFloat(element.ry) || 0,
      cx = parseFloat(element.cx) || 0,
      cy = parseFloat(element.cy) || 0,
      startX = cx - rx,
      startY = cy,
      endX = parseFloat(cx) + parseFloat(rx),
      endY = cy;

  newElement.d = 'M' + startX + ',' + startY +
                 'A' + rx + ',' + ry + ' 0,1,1 ' + endX + ',' + endY +
                 'A' + rx + ',' + ry + ' 0,1,1 ' + startX + ',' + endY;
  return newElement;
};

/**
 * Read `circle` element to extract and transform
 * data, to make it ready for a `path` object.
 *
 * @param  {DOMelement} element Circle element to transform
 * @return {object}             Data for a `path` element
 */
Pathformer.prototype.circleToPath = function (element) {
  var newElement = {},
      r  = parseFloat(element.r)  || 0,
      cx = parseFloat(element.cx) || 0,
      cy = parseFloat(element.cy) || 0,
      startX = cx - r,
      startY = cy,
      endX = parseFloat(cx) + parseFloat(r),
      endY = cy;
      
  newElement.d =  'M' + startX + ',' + startY +
                  'A' + r + ',' + r + ' 0,1,1 ' + endX + ',' + endY +
                  'A' + r + ',' + r + ' 0,1,1 ' + startX + ',' + endY;
  return newElement;
};

/**
 * Create `path` elements form original element
 * and prepared objects
 *
 * @param  {DOMelement} element  Original element to transform
 * @param  {object} pathData     Path data (from `toPath` methods)
 * @return {DOMelement}          Path element
 */
Pathformer.prototype.pathMaker = function (element, pathData) {
  var i, attr, pathTag = document.createElementNS('http://www.w3.org/2000/svg','path');
  for(i = 0; i < element.attributes.length; i++) {
    attr = element.attributes[i];
    if (this.ATTR_WATCH.indexOf(attr.name) === -1) {
      pathTag.setAttribute(attr.name, attr.value);
    }
  }
  for(i in pathData) {
    pathTag.setAttribute(i, pathData[i]);
  }
  return pathTag;
};

/**
 * Parse attributes of a DOM element to
 * get an object of attribute => value
 *
 * @param  {NamedNodeMap} attributes Attributes object from DOM element to parse
 * @return {object}                  Object of attributes
 */
Pathformer.prototype.parseAttr = function (element) {
  var attr, output = {};
  for (var i = 0; i < element.length; i++) {
    attr = element[i];
    // Check if no data attribute contains '%', or the transformation is impossible
    if (this.ATTR_WATCH.indexOf(attr.name) !== -1 && attr.value.indexOf('%') !== -1) {
      throw new Error('Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into \'path\' tags. Please use \'viewBox\'.');
    }
    output[attr.name] = attr.value;
  }
  return output;
};

  'use strict';

var requestAnimFrame, cancelAnimFrame, parsePositiveInt;

/**
 * Vivus
 * Beta version
 *
 * Take any SVG and make the animation
 * to give give the impression of live drawing
 *
 * This in more than just inspired from codrops
 * At that point, it's a pure fork.
 */

/**
 * Class constructor
 * option structure
 *   type: 'delayed'|'sync'|'oneByOne'|'script' (to know if the items must be drawn synchronously or not, default: delayed)
 *   duration: <int> (in frames)
 *   start: 'inViewport'|'manual'|'autostart' (start automatically the animation, default: inViewport)
 *   delay: <int> (delay between the drawing of first and last path)
 *   dashGap <integer> whitespace extra margin between dashes
 *   pathTimingFunction <function> timing animation function for each path element of the SVG
 *   animTimingFunction <function> timing animation function for the complete SVG
 *   forceRender <boolean> force the browser to re-render all updated path items
 *   selfDestroy <boolean> removes all extra styling on the SVG, and leaves it as original
 *
 * The attribute 'type' is by default on 'delayed'.
 *  - 'delayed'
 *    all paths are draw at the same time but with a
 *    little delay between them before start
 *  - 'sync'
 *    all path are start and finish at the same time
 *  - 'oneByOne'
 *    only one path is draw at the time
 *    the end of the first one will trigger the draw
 *    of the next one
 *
 * All these values can be overwritten individually
 * for each path item in the SVG
 * The value of frames will always take the advantage of
 * the duration value.
 * If you fail somewhere, an error will be thrown.
 * Good luck.
 *
 * @constructor
 * @this {Vivus}
 * @param {DOM|String}   element  Dom element of the SVG or id of it
 * @param {Object}       options  Options about the animation
 * @param {Function}     callback Callback for the end of the animation
 */
function Vivus (element, options, callback) {

  // Setup
  this.isReady = false;
  this.setElement(element, options);
  this.setOptions(options);
  this.setCallback(callback);

  if (this.isReady) {
    this.init();
  }
}

/**
 * Timing functions
 **************************************
 *
 * Default functions to help developers.
 * It always take a number as parameter (between 0 to 1) then
 * return a number (between 0 and 1)
 */
Vivus.LINEAR          = function (x) {return x;};
Vivus.EASE            = function (x) {return -Math.cos(x * Math.PI) / 2 + 0.5;};
Vivus.EASE_OUT        = function (x) {return 1 - Math.pow(1-x, 3);};
Vivus.EASE_IN         = function (x) {return Math.pow(x, 3);};
Vivus.EASE_OUT_BOUNCE = function (x) {
  var base = -Math.cos(x * (0.5 * Math.PI)) + 1,
    rate = Math.pow(base,1.5),
    rateR = Math.pow(1 - x, 2),
    progress = -Math.abs(Math.cos(rate * (2.5 * Math.PI) )) + 1;
  return (1- rateR) + (progress * rateR);
};


/**
 * Setters
 **************************************
 */

/**
 * Check and set the element in the instance
 * The method will not return anything, but will throw an
 * error if the parameter is invalid
 *
 * @param {DOM|String}   element  SVG Dom element or id of it
 */
Vivus.prototype.setElement = function (element, options) {
  // Basic check
  if (typeof element === 'undefined') {
    throw new Error('Vivus [constructor]: "element" parameter is required');
  }

  // Set the element
  if (element.constructor === String) {
    element = document.getElementById(element);
    if (!element) {
      throw new Error('Vivus [constructor]: "element" parameter is not related to an existing ID');
    }
  }
  this.parentEl = element;

  // Create the object element if the property `file` exists in the options object
  if (options && options.file) {
    var objElm = document.createElement('object');
    objElm.setAttribute('type', 'image/svg+xml');
    objElm.setAttribute('data', options.file);
    objElm.setAttribute('built-by-vivus', 'true');
    element.appendChild(objElm);
    element = objElm;
  }
  switch (element.constructor) {
  case window.SVGSVGElement:
  case window.SVGElement:
    this.el = element;
    this.isReady = true;
    break;

  case window.HTMLObjectElement:
    // If we have to wait for it
    var onLoad, self;

    self = this;
    onLoad = function (e) {
      if (self.isReady) {
        return;
      }
      self.el = element.contentDocument && element.contentDocument.querySelector('svg');
      if (!self.el && e) {
        throw new Error('Vivus [constructor]: object loaded does not contain any SVG');
      }
      else if (self.el) {
        if (element.getAttribute('built-by-vivus')) {
          self.parentEl.insertBefore(self.el, element);
          self.parentEl.removeChild(element);
          self.el.setAttribute('width', '100%');
          self.el.setAttribute('height', '100%');
        }
        self.isReady = true;
        self.init();
        return true;
      }
    };

    if (!onLoad()) {
      element.addEventListener('load', onLoad);
    }
    break;

  default:
    if (!element){
      throw new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)');
    }
  }
};

/**
 * Set up user option to the instance
 * The method will not return anything, but will throw an
 * error if the parameter is invalid
 *
 * @param  {object} options Object from the constructor
 */
Vivus.prototype.setOptions = function (options) {
  var allowedTypes = ['delayed', 'sync', 'async', 'nsync', 'oneByOne', 'scenario', 'scenario-sync'];
  var allowedStarts =  ['inViewport', 'manual', 'autostart'];

  // Basic check
  if (options !== undefined && options.constructor !== Object) {
    throw new Error('Vivus [constructor]: "options" parameter must be an object');
  }
  else {
    options = options || {};
  }

  // Set the animation type
  if (options.type && allowedTypes.indexOf(options.type) === -1) {
    throw new Error('Vivus [constructor]: ' + options.type + ' is not an existing animation `type`');
  }
  else {
    this.type = options.type || allowedTypes[0];
  }

  // Set the start type
  if (options.start && allowedStarts.indexOf(options.start) === -1) {
    throw new Error('Vivus [constructor]: ' + options.start + ' is not an existing `start` option');
  }
  else {
    this.start = options.start || allowedStarts[0];
  }

  this.isIE         = (window.navigator.userAgent.indexOf('MSIE') !== -1 || window.navigator.userAgent.indexOf('Trident/') !== -1 || window.navigator.userAgent.indexOf('Edge/') !== -1 );
  this.duration     = parsePositiveInt(options.duration, 120);
  this.delay        = parsePositiveInt(options.delay, null);
  this.dashGap      = parsePositiveInt(options.dashGap, 1);
  this.forceRender  = options.hasOwnProperty('forceRender') ? !!options.forceRender : this.isIE;
  this.reverseStack = !!options.reverseStack;
  this.selfDestroy  = !!options.selfDestroy;
  this.onReady      = options.onReady;
  this.map          = [];
  this.frameLength  = this.currentFrame = this.delayUnit = this.speed = this.handle = null;

  this.ignoreInvisible = options.hasOwnProperty('ignoreInvisible') ? !!options.ignoreInvisible : false;

  this.animTimingFunction = options.animTimingFunction || Vivus.LINEAR;
  this.pathTimingFunction = options.pathTimingFunction || Vivus.LINEAR;

  if (this.delay >= this.duration) {
    throw new Error('Vivus [constructor]: delay must be shorter than duration');
  }
};

/**
 * Set up callback to the instance
 * The method will not return enything, but will throw an
 * error if the parameter is invalid
 *
 * @param  {Function} callback Callback for the animation end
 */
Vivus.prototype.setCallback = function (callback) {
  // Basic check
  if (!!callback && callback.constructor !== Function) {
    throw new Error('Vivus [constructor]: "callback" parameter must be a function');
  }
  this.callback = callback || function () {};
};


/**
 * Core
 **************************************
 */

/**
 * Map the svg, path by path.
 * The method return nothing, it just fill the
 * `map` array. Each item in this array represent
 * a path element from the SVG, with informations for
 * the animation.
 *
 * ```
 * [
 *   {
 *     el: <DOMobj> the path element
 *     length: <number> length of the path line
 *     startAt: <number> time start of the path animation (in frames)
 *     duration: <number> path animation duration (in frames)
 *   },
 *   ...
 * ]
 * ```
 *
 */
Vivus.prototype.mapping = function () {
  var i, paths, path, pAttrs, pathObj, totalLength, lengthMeter, timePoint;
  timePoint = totalLength = lengthMeter = 0;
  paths = this.el.querySelectorAll('path');

  for (i = 0; i < paths.length; i++) {
    path = paths[i];
    if (this.isInvisible(path)) {
      continue;
    }
    pathObj = {
      el: path,
      length: Math.ceil(path.getTotalLength())
    };
    // Test if the path length is correct
    if (isNaN(pathObj.length)) {
      if (window.console && console.warn) {
        console.warn('Vivus [mapping]: cannot retrieve a path element length', path);
      }
      continue;
    }
    this.map.push(pathObj);
    path.style.strokeDasharray  = pathObj.length + ' ' + (pathObj.length + this.dashGap * 2);
    path.style.strokeDashoffset = pathObj.length + this.dashGap;
    pathObj.length += this.dashGap;
    totalLength += pathObj.length;

    this.renderPath(i);
  }

  totalLength = totalLength === 0 ? 1 : totalLength;
  this.delay = this.delay === null ? this.duration / 3 : this.delay;
  this.delayUnit = this.delay / (paths.length > 1 ? paths.length - 1 : 1);

  // Reverse stack if asked
  if (this.reverseStack) {
    this.map.reverse();
  }

  for (i = 0; i < this.map.length; i++) {
    pathObj = this.map[i];

    switch (this.type) {
    case 'delayed':
      pathObj.startAt = this.delayUnit * i;
      pathObj.duration = this.duration - this.delay;
      break;

    case 'oneByOne':
      pathObj.startAt = lengthMeter / totalLength * this.duration;
      pathObj.duration = pathObj.length / totalLength * this.duration;
      break;

    case 'sync':
    case 'async':
    case 'nsync':
      pathObj.startAt = 0;
      pathObj.duration = this.duration;
      break;

    case 'scenario-sync':
      path = pathObj.el;
      pAttrs = this.parseAttr(path);
      pathObj.startAt = timePoint + (parsePositiveInt(pAttrs['data-delay'], this.delayUnit) || 0);
      pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration);
      timePoint = pAttrs['data-async'] !== undefined ? pathObj.startAt : pathObj.startAt + pathObj.duration;
      this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration));
      break;

    case 'scenario':
      path = pathObj.el;
      pAttrs = this.parseAttr(path);
      pathObj.startAt = parsePositiveInt(pAttrs['data-start'], this.delayUnit) || 0;
      pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration);
      this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration));
      break;
    }
    lengthMeter += pathObj.length;
    this.frameLength = this.frameLength || this.duration;
  }
};

/**
 * Interval method to draw the SVG from current
 * position of the animation. It update the value of
 * `currentFrame` and re-trace the SVG.
 *
 * It use this.handle to store the requestAnimationFrame
 * and clear it one the animation is stopped. So this
 * attribute can be used to know if the animation is
 * playing.
 *
 * Once the animation at the end, this method will
 * trigger the Vivus callback.
 *
 */
Vivus.prototype.drawer = function () {
  var self = this;
  this.currentFrame += this.speed;

  if (this.currentFrame <= 0) {
    this.stop();
    this.reset();
  } else if (this.currentFrame >= this.frameLength) {
    this.stop();
    this.currentFrame = this.frameLength;
    this.trace();
    if (this.selfDestroy) {
      this.destroy();
    }
  } else {
    this.trace();
    this.handle = requestAnimFrame(function () {
      self.drawer();
    });
    return;
  }

  this.callback(this);
  if (this.instanceCallback) {
    this.instanceCallback(this);
    this.instanceCallback = null;
  }
};

/**
 * Draw the SVG at the current instant from the
 * `currentFrame` value. Here is where most of the magic is.
 * The trick is to use the `strokeDashoffset` style property.
 *
 * For optimisation reasons, a new property called `progress`
 * is added in each item of `map`. This one contain the current
 * progress of the path element. Only if the new value is different
 * the new value will be applied to the DOM element. This
 * method save a lot of resources to re-render the SVG. And could
 * be improved if the animation couldn't be played forward.
 *
 */
Vivus.prototype.trace = function () {
  var i, progress, path, currentFrame;
  currentFrame = this.animTimingFunction(this.currentFrame / this.frameLength) * this.frameLength;
  for (i = 0; i < this.map.length; i++) {
    path = this.map[i];
    progress = (currentFrame - path.startAt) / path.duration;
    progress = this.pathTimingFunction(Math.max(0, Math.min(1, progress)));
    if (path.progress !== progress) {
      path.progress = progress;
      path.el.style.strokeDashoffset = Math.floor(path.length * (1 - progress));
      this.renderPath(i);
    }
  }
};

/**
 * Method forcing the browser to re-render a path element
 * from it's index in the map. Depending on the `forceRender`
 * value.
 * The trick is to replace the path element by it's clone.
 * This practice is not recommended because it's asking more
 * ressources, too much DOM manupulation..
 * but it's the only way to let the magic happen on IE.
 * By default, this fallback is only applied on IE.
 *
 * @param  {Number} index Path index
 */
Vivus.prototype.renderPath = function (index) {
  if (this.forceRender && this.map && this.map[index]) {
    var pathObj = this.map[index],
        newPath = pathObj.el.cloneNode(true);
    pathObj.el.parentNode.replaceChild(newPath, pathObj.el);
    pathObj.el = newPath;
  }
};

/**
 * When the SVG object is loaded and ready,
 * this method will continue the initialisation.
 *
 * This this mainly due to the case of passing an
 * object tag in the constructor. It will wait
 * the end of the loading to initialise.
 *
 */
Vivus.prototype.init = function () {
  // Set object variables
  this.frameLength = 0;
  this.currentFrame = 0;
  this.map = [];

  // Start
  new Pathformer(this.el);
  this.mapping();
  this.starter();

  if (this.onReady) {
    this.onReady(this);
  }
};

/**
 * Trigger to start of the animation.
 * Depending on the `start` value, a different script
 * will be applied.
 *
 * If the `start` value is not valid, an error will be thrown.
 * Even if technically, this is impossible.
 *
 */
Vivus.prototype.starter = function () {
  switch (this.start) {
  case 'manual':
    return;

  case 'autostart':
    this.play();
    break;

  case 'inViewport':
    var self = this,
    listener = function () {
      if (self.isInViewport(self.parentEl, 1)) {
        self.play();
        window.removeEventListener('scroll', listener);
      }
    };
    window.addEventListener('scroll', listener);
    listener();
    break;
  }
};


/**
 * Controls
 **************************************
 */

/**
 * Get the current status of the animation between
 * three different states: 'start', 'progress', 'end'.
 * @return {string} Instance status
 */
Vivus.prototype.getStatus = function () {
  return this.currentFrame === 0 ? 'start' : this.currentFrame === this.frameLength ? 'end' : 'progress';
};

/**
 * Reset the instance to the initial state : undraw
 * Be careful, it just reset the animation, if you're
 * playing the animation, this won't stop it. But just
 * make it start from start.
 *
 */
Vivus.prototype.reset = function () {
  return this.setFrameProgress(0);
};

/**
 * Set the instance to the final state : drawn
 * Be careful, it just set the animation, if you're
 * playing the animation on rewind, this won't stop it.
 * But just make it start from the end.
 *
 */
Vivus.prototype.finish = function () {
  return this.setFrameProgress(1);
};

/**
 * Set the level of progress of the drawing.
 *
 * @param {number} progress Level of progress to set
 */
Vivus.prototype.setFrameProgress = function (progress) {
  progress = Math.min(1, Math.max(0, progress));
  this.currentFrame = Math.round(this.frameLength * progress);
  this.trace();
  return this;
};

/**
 * Play the animation at the desired speed.
 * Speed must be a valid number (no zero).
 * By default, the speed value is 1.
 * But a negative value is accepted to go forward.
 *
 * And works with float too.
 * But don't forget we are in JavaScript, se be nice
 * with him and give him a 1/2^x value.
 *
 * @param  {number} speed Animation speed [optional]
 */
Vivus.prototype.play = function (speed, callback) {
  this.instanceCallback = null;

  if (speed && typeof speed === 'function') {
    this.instanceCallback = speed; // first parameter is actually the callback function
    speed = null;
  }
  else if (speed && typeof speed !== 'number') {
    throw new Error('Vivus [play]: invalid speed');
  }
  // if the first parameter wasn't the callback, check if the seconds was
  if (callback && typeof(callback) === 'function' && !this.instanceCallback) {
    this.instanceCallback = callback;
  }


  this.speed = speed || 1;
  if (!this.handle) {
    this.drawer();
  }
  return this;
};

/**
 * Stop the current animation, if on progress.
 * Should not trigger any error.
 *
 */
Vivus.prototype.stop = function () {
  if (this.handle) {
    cancelAnimFrame(this.handle);
    this.handle = null;
  }
  return this;
};

/**
 * Destroy the instance.
 * Remove all bad styling attributes on all
 * path tags
 *
 */
Vivus.prototype.destroy = function () {
  this.stop();
  var i, path;
  for (i = 0; i < this.map.length; i++) {
    path = this.map[i];
    path.el.style.strokeDashoffset = null;
    path.el.style.strokeDasharray = null;
    this.renderPath(i);
  }
};


/**
 * Utils methods
 * include methods from Codrops
 **************************************
 */

/**
 * Method to best guess if a path should added into
 * the animation or not.
 *
 * 1. Use the `data-vivus-ignore` attribute if set
 * 2. Check if the instance must ignore invisible paths
 * 3. Check if the path is visible
 *
 * For now the visibility checking is unstable.
 * It will be used for a beta phase.
 *
 * Other improvments are planned. Like detecting
 * is the path got a stroke or a valid opacity.
 */
Vivus.prototype.isInvisible = function (el) {
  var rect,
    ignoreAttr = el.getAttribute('data-ignore');

  if (ignoreAttr !== null) {
    return ignoreAttr !== 'false';
  }

  if (this.ignoreInvisible) {
    rect = el.getBoundingClientRect();
    return !rect.width && !rect.height;
  }
  else {
    return false;
  }
};

/**
 * Parse attributes of a DOM element to
 * get an object of {attributeName => attributeValue}
 *
 * @param  {object} element DOM element to parse
 * @return {object}         Object of attributes
 */
Vivus.prototype.parseAttr = function (element) {
  var attr, output = {};
  if (element && element.attributes) {
    for (var i = 0; i < element.attributes.length; i++) {
      attr = element.attributes[i];
      output[attr.name] = attr.value;
    }
  }
  return output;
};

/**
 * Reply if an element is in the page viewport
 *
 * @param  {object} el Element to observe
 * @param  {number} h  Percentage of height
 * @return {boolean}
 */
Vivus.prototype.isInViewport = function (el, h) {
  var scrolled   = this.scrollY(),
    viewed       = scrolled + this.getViewportH(),
    elBCR        = el.getBoundingClientRect(),
    elHeight     = elBCR.height,
    elTop        = scrolled + elBCR.top,
    elBottom     = elTop + elHeight;

  // if 0, the element is considered in the viewport as soon as it enters.
  // if 1, the element is considered in the viewport only when it's fully inside
  // value in percentage (1 >= h >= 0)
  h = h || 0;

  return (elTop + elHeight * h) <= viewed && (elBottom) >= scrolled;
};

/**
 * Alias for document element
 *
 * @type {DOMelement}
 */
Vivus.prototype.docElem = window.document.documentElement;

/**
 * Get the viewport height in pixels
 *
 * @return {integer} Viewport height
 */
Vivus.prototype.getViewportH = function () {
  var client = this.docElem.clientHeight,
    inner = window.innerHeight;

  if (client < inner) {
    return inner;
  }
  else {
    return client;
  }
};

/**
 * Get the page Y offset
 *
 * @return {integer} Page Y offset
 */
Vivus.prototype.scrollY = function () {
  return window.pageYOffset || this.docElem.scrollTop;
};

/**
 * Alias for `requestAnimationFrame` or
 * `setTimeout` function for deprecated browsers.
 *
 */
requestAnimFrame = (function () {
  return (
    window.requestAnimationFrame       ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame    ||
    window.oRequestAnimationFrame      ||
    window.msRequestAnimationFrame     ||
    function(/* function */ callback){
      return window.setTimeout(callback, 1000 / 60);
    }
  );
})();

/**
 * Alias for `cancelAnimationFrame` or
 * `cancelTimeout` function for deprecated browsers.
 *
 */
cancelAnimFrame = (function () {
  return (
    window.cancelAnimationFrame       ||
    window.webkitCancelAnimationFrame ||
    window.mozCancelAnimationFrame    ||
    window.oCancelAnimationFrame      ||
    window.msCancelAnimationFrame     ||
    function(id){
      return window.clearTimeout(id);
    }
  );
})();

/**
 * Parse string to integer.
 * If the number is not positive or null
 * the method will return the default value
 * or 0 if undefined
 *
 * @param {string} value String to parse
 * @param {*} defaultValue Value to return if the result parsed is invalid
 * @return {number}
 *
 */
parsePositiveInt = function (value, defaultValue) {
  var output = parseInt(value, 10);
  return (output >= 0) ? output : defaultValue;
};


  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define([], function() {
      return Vivus;
    });
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = Vivus;
  } else {
    // Browser globals
    window.Vivus = Vivus;
  }

}(window, document));

/*! @vimeo/player v2.18.0 | (c) 2022 Vimeo | MIT License | https://github.com/vimeo/player.js */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
            (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (global.Vimeo = global.Vimeo || {}, global.Vimeo.Player = factory()));
}(this, (function () { 'use strict';

    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) {
            throw new TypeError("Cannot call a class as a function");
        }
    }

    function _defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }

    function _createClass(Constructor, protoProps, staticProps) {
        if (protoProps) _defineProperties(Constructor.prototype, protoProps);
        if (staticProps) _defineProperties(Constructor, staticProps);
        return Constructor;
    }

    /**
     * @module lib/functions
     */

    /**
     * Check to see this is a node environment.
     * @type {Boolean}
     */

    /* global global */
    var isNode = typeof global !== 'undefined' && {}.toString.call(global) === '[object global]';
    /**
     * Get the name of the method for a given getter or setter.
     *
     * @param {string} prop The name of the property.
     * @param {string} type Either “get” or “set”.
     * @return {string}
     */

    function getMethodName(prop, type) {
        if (prop.indexOf(type.toLowerCase()) === 0) {
            return prop;
        }

        return "".concat(type.toLowerCase()).concat(prop.substr(0, 1).toUpperCase()).concat(prop.substr(1));
    }
    /**
     * Check to see if the object is a DOM Element.
     *
     * @param {*} element The object to check.
     * @return {boolean}
     */

    function isDomElement(element) {
        return Boolean(element && element.nodeType === 1 && 'nodeName' in element && element.ownerDocument && element.ownerDocument.defaultView);
    }
    /**
     * Check to see whether the value is a number.
     *
     * @see http://dl.dropboxusercontent.com/u/35146/js/tests/isNumber.html
     * @param {*} value The value to check.
     * @param {boolean} integer Check if the value is an integer.
     * @return {boolean}
     */

    function isInteger(value) {
        // eslint-disable-next-line eqeqeq
        return !isNaN(parseFloat(value)) && isFinite(value) && Math.floor(value) == value;
    }
    /**
     * Check to see if the URL is a Vimeo url.
     *
     * @param {string} url The url string.
     * @return {boolean}
     */

    function isVimeoUrl(url) {
        return /^(https?:)?\/\/((player|www)\.)?vimeo\.com(?=$|\/)/.test(url);
    }
    /**
     * Check to see if the URL is for a Vimeo embed.
     *
     * @param {string} url The url string.
     * @return {boolean}
     */

    function isVimeoEmbed(url) {
        var expr = /^https:\/\/player\.vimeo\.com\/video\/\d+/;
        return expr.test(url);
    }
    /**
     * Get the Vimeo URL from an element.
     * The element must have either a data-vimeo-id or data-vimeo-url attribute.
     *
     * @param {object} oEmbedParameters The oEmbed parameters.
     * @return {string}
     */

    function getVimeoUrl() {
        var oEmbedParameters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        var id = oEmbedParameters.id;
        var url = oEmbedParameters.url;
        var idOrUrl = id || url;

        if (!idOrUrl) {
            throw new Error('An id or url must be passed, either in an options object or as a data-vimeo-id or data-vimeo-url attribute.');
        }

        if (isInteger(idOrUrl)) {
            return "https://vimeo.com/".concat(idOrUrl);
        }

        if (isVimeoUrl(idOrUrl)) {
            return idOrUrl.replace('http:', 'https:');
        }

        if (id) {
            throw new TypeError("\u201C".concat(id, "\u201D is not a valid video id."));
        }

        throw new TypeError("\u201C".concat(idOrUrl, "\u201D is not a vimeo.com url."));
    }

    var arrayIndexOfSupport = typeof Array.prototype.indexOf !== 'undefined';
    var postMessageSupport = typeof window !== 'undefined' && typeof window.postMessage !== 'undefined';

    if (!isNode && (!arrayIndexOfSupport || !postMessageSupport)) {
        throw new Error('Sorry, the Vimeo Player API is not available in this browser.');
    }

    var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

    function createCommonjsModule(fn, module) {
        return module = { exports: {} }, fn(module, module.exports), module.exports;
    }

    /*!
     * weakmap-polyfill v2.0.4 - ECMAScript6 WeakMap polyfill
     * https://github.com/polygonplanet/weakmap-polyfill
     * Copyright (c) 2015-2021 polygonplanet <polygon.planet.aqua@gmail.com>
     * @license MIT
     */
    (function (self) {

        if (self.WeakMap) {
            return;
        }

        var hasOwnProperty = Object.prototype.hasOwnProperty;

        var hasDefine = Object.defineProperty && function () {
            try {
                // Avoid IE8's broken Object.defineProperty
                return Object.defineProperty({}, 'x', {
                    value: 1
                }).x === 1;
            } catch (e) {}
        }();

        var defineProperty = function (object, name, value) {
            if (hasDefine) {
                Object.defineProperty(object, name, {
                    configurable: true,
                    writable: true,
                    value: value
                });
            } else {
                object[name] = value;
            }
        };

        self.WeakMap = function () {
            // ECMA-262 23.3 WeakMap Objects
            function WeakMap() {
                if (this === void 0) {
                    throw new TypeError("Constructor WeakMap requires 'new'");
                }

                defineProperty(this, '_id', genId('_WeakMap')); // ECMA-262 23.3.1.1 WeakMap([iterable])

                if (arguments.length > 0) {
                    // Currently, WeakMap `iterable` argument is not supported
                    throw new TypeError('WeakMap iterable is not supported');
                }
            } // ECMA-262 23.3.3.2 WeakMap.prototype.delete(key)


            defineProperty(WeakMap.prototype, 'delete', function (key) {
                checkInstance(this, 'delete');

                if (!isObject(key)) {
                    return false;
                }

                var entry = key[this._id];

                if (entry && entry[0] === key) {
                    delete key[this._id];
                    return true;
                }

                return false;
            }); // ECMA-262 23.3.3.3 WeakMap.prototype.get(key)

            defineProperty(WeakMap.prototype, 'get', function (key) {
                checkInstance(this, 'get');

                if (!isObject(key)) {
                    return void 0;
                }

                var entry = key[this._id];

                if (entry && entry[0] === key) {
                    return entry[1];
                }

                return void 0;
            }); // ECMA-262 23.3.3.4 WeakMap.prototype.has(key)

            defineProperty(WeakMap.prototype, 'has', function (key) {
                checkInstance(this, 'has');

                if (!isObject(key)) {
                    return false;
                }

                var entry = key[this._id];

                if (entry && entry[0] === key) {
                    return true;
                }

                return false;
            }); // ECMA-262 23.3.3.5 WeakMap.prototype.set(key, value)

            defineProperty(WeakMap.prototype, 'set', function (key, value) {
                checkInstance(this, 'set');

                if (!isObject(key)) {
                    throw new TypeError('Invalid value used as weak map key');
                }

                var entry = key[this._id];

                if (entry && entry[0] === key) {
                    entry[1] = value;
                    return this;
                }

                defineProperty(key, this._id, [key, value]);
                return this;
            });

            function checkInstance(x, methodName) {
                if (!isObject(x) || !hasOwnProperty.call(x, '_id')) {
                    throw new TypeError(methodName + ' method called on incompatible receiver ' + typeof x);
                }
            }

            function genId(prefix) {
                return prefix + '_' + rand() + '.' + rand();
            }

            function rand() {
                return Math.random().toString().substring(2);
            }

            defineProperty(WeakMap, '_polyfill', true);
            return WeakMap;
        }();

        function isObject(x) {
            return Object(x) === x;
        }
    })(typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : commonjsGlobal);

    var npo_src = createCommonjsModule(function (module) {
        /*! Native Promise Only
            v0.8.1 (c) Kyle Simpson
            MIT License: http://getify.mit-license.org
        */
        (function UMD(name, context, definition) {
            // special form of UMD for polyfilling across evironments
            context[name] = context[name] || definition();

            if ( module.exports) {
                module.exports = context[name];
            }
        })("Promise", typeof commonjsGlobal != "undefined" ? commonjsGlobal : commonjsGlobal, function DEF() {

            var builtInProp,
                cycle,
                scheduling_queue,
                ToString = Object.prototype.toString,
                timer = typeof setImmediate != "undefined" ? function timer(fn) {
                    return setImmediate(fn);
                } : setTimeout; // dammit, IE8.

            try {
                Object.defineProperty({}, "x", {});

                builtInProp = function builtInProp(obj, name, val, config) {
                    return Object.defineProperty(obj, name, {
                        value: val,
                        writable: true,
                        configurable: config !== false
                    });
                };
            } catch (err) {
                builtInProp = function builtInProp(obj, name, val) {
                    obj[name] = val;
                    return obj;
                };
            } // Note: using a queue instead of array for efficiency


            scheduling_queue = function Queue() {
                var first, last, item;

                function Item(fn, self) {
                    this.fn = fn;
                    this.self = self;
                    this.next = void 0;
                }

                return {
                    add: function add(fn, self) {
                        item = new Item(fn, self);

                        if (last) {
                            last.next = item;
                        } else {
                            first = item;
                        }

                        last = item;
                        item = void 0;
                    },
                    drain: function drain() {
                        var f = first;
                        first = last = cycle = void 0;

                        while (f) {
                            f.fn.call(f.self);
                            f = f.next;
                        }
                    }
                };
            }();

            function schedule(fn, self) {
                scheduling_queue.add(fn, self);

                if (!cycle) {
                    cycle = timer(scheduling_queue.drain);
                }
            } // promise duck typing


            function isThenable(o) {
                var _then,
                    o_type = typeof o;

                if (o != null && (o_type == "object" || o_type == "function")) {
                    _then = o.then;
                }

                return typeof _then == "function" ? _then : false;
            }

            function notify() {
                for (var i = 0; i < this.chain.length; i++) {
                    notifyIsolated(this, this.state === 1 ? this.chain[i].success : this.chain[i].failure, this.chain[i]);
                }

                this.chain.length = 0;
            } // NOTE: This is a separate function to isolate
            // the `try..catch` so that other code can be
            // optimized better


            function notifyIsolated(self, cb, chain) {
                var ret, _then;

                try {
                    if (cb === false) {
                        chain.reject(self.msg);
                    } else {
                        if (cb === true) {
                            ret = self.msg;
                        } else {
                            ret = cb.call(void 0, self.msg);
                        }

                        if (ret === chain.promise) {
                            chain.reject(TypeError("Promise-chain cycle"));
                        } else if (_then = isThenable(ret)) {
                            _then.call(ret, chain.resolve, chain.reject);
                        } else {
                            chain.resolve(ret);
                        }
                    }
                } catch (err) {
                    chain.reject(err);
                }
            }

            function resolve(msg) {
                var _then,
                    self = this; // already triggered?


                if (self.triggered) {
                    return;
                }

                self.triggered = true; // unwrap

                if (self.def) {
                    self = self.def;
                }

                try {
                    if (_then = isThenable(msg)) {
                        schedule(function () {
                            var def_wrapper = new MakeDefWrapper(self);

                            try {
                                _then.call(msg, function $resolve$() {
                                    resolve.apply(def_wrapper, arguments);
                                }, function $reject$() {
                                    reject.apply(def_wrapper, arguments);
                                });
                            } catch (err) {
                                reject.call(def_wrapper, err);
                            }
                        });
                    } else {
                        self.msg = msg;
                        self.state = 1;

                        if (self.chain.length > 0) {
                            schedule(notify, self);
                        }
                    }
                } catch (err) {
                    reject.call(new MakeDefWrapper(self), err);
                }
            }

            function reject(msg) {
                var self = this; // already triggered?

                if (self.triggered) {
                    return;
                }

                self.triggered = true; // unwrap

                if (self.def) {
                    self = self.def;
                }

                self.msg = msg;
                self.state = 2;

                if (self.chain.length > 0) {
                    schedule(notify, self);
                }
            }

            function iteratePromises(Constructor, arr, resolver, rejecter) {
                for (var idx = 0; idx < arr.length; idx++) {
                    (function IIFE(idx) {
                        Constructor.resolve(arr[idx]).then(function $resolver$(msg) {
                            resolver(idx, msg);
                        }, rejecter);
                    })(idx);
                }
            }

            function MakeDefWrapper(self) {
                this.def = self;
                this.triggered = false;
            }

            function MakeDef(self) {
                this.promise = self;
                this.state = 0;
                this.triggered = false;
                this.chain = [];
                this.msg = void 0;
            }

            function Promise(executor) {
                if (typeof executor != "function") {
                    throw TypeError("Not a function");
                }

                if (this.__NPO__ !== 0) {
                    throw TypeError("Not a promise");
                } // instance shadowing the inherited "brand"
                // to signal an already "initialized" promise


                this.__NPO__ = 1;
                var def = new MakeDef(this);

                this["then"] = function then(success, failure) {
                    var o = {
                        success: typeof success == "function" ? success : true,
                        failure: typeof failure == "function" ? failure : false
                    }; // Note: `then(..)` itself can be borrowed to be used against
                    // a different promise constructor for making the chained promise,
                    // by substituting a different `this` binding.

                    o.promise = new this.constructor(function extractChain(resolve, reject) {
                        if (typeof resolve != "function" || typeof reject != "function") {
                            throw TypeError("Not a function");
                        }

                        o.resolve = resolve;
                        o.reject = reject;
                    });
                    def.chain.push(o);

                    if (def.state !== 0) {
                        schedule(notify, def);
                    }

                    return o.promise;
                };

                this["catch"] = function $catch$(failure) {
                    return this.then(void 0, failure);
                };

                try {
                    executor.call(void 0, function publicResolve(msg) {
                        resolve.call(def, msg);
                    }, function publicReject(msg) {
                        reject.call(def, msg);
                    });
                } catch (err) {
                    reject.call(def, err);
                }
            }

            var PromisePrototype = builtInProp({}, "constructor", Promise,
                /*configurable=*/
                false); // Note: Android 4 cannot use `Object.defineProperty(..)` here

            Promise.prototype = PromisePrototype; // built-in "brand" to signal an "uninitialized" promise

            builtInProp(PromisePrototype, "__NPO__", 0,
                /*configurable=*/
                false);
            builtInProp(Promise, "resolve", function Promise$resolve(msg) {
                var Constructor = this; // spec mandated checks
                // note: best "isPromise" check that's practical for now

                if (msg && typeof msg == "object" && msg.__NPO__ === 1) {
                    return msg;
                }

                return new Constructor(function executor(resolve, reject) {
                    if (typeof resolve != "function" || typeof reject != "function") {
                        throw TypeError("Not a function");
                    }

                    resolve(msg);
                });
            });
            builtInProp(Promise, "reject", function Promise$reject(msg) {
                return new this(function executor(resolve, reject) {
                    if (typeof resolve != "function" || typeof reject != "function") {
                        throw TypeError("Not a function");
                    }

                    reject(msg);
                });
            });
            builtInProp(Promise, "all", function Promise$all(arr) {
                var Constructor = this; // spec mandated checks

                if (ToString.call(arr) != "[object Array]") {
                    return Constructor.reject(TypeError("Not an array"));
                }

                if (arr.length === 0) {
                    return Constructor.resolve([]);
                }

                return new Constructor(function executor(resolve, reject) {
                    if (typeof resolve != "function" || typeof reject != "function") {
                        throw TypeError("Not a function");
                    }

                    var len = arr.length,
                        msgs = Array(len),
                        count = 0;
                    iteratePromises(Constructor, arr, function resolver(idx, msg) {
                        msgs[idx] = msg;

                        if (++count === len) {
                            resolve(msgs);
                        }
                    }, reject);
                });
            });
            builtInProp(Promise, "race", function Promise$race(arr) {
                var Constructor = this; // spec mandated checks

                if (ToString.call(arr) != "[object Array]") {
                    return Constructor.reject(TypeError("Not an array"));
                }

                return new Constructor(function executor(resolve, reject) {
                    if (typeof resolve != "function" || typeof reject != "function") {
                        throw TypeError("Not a function");
                    }

                    iteratePromises(Constructor, arr, function resolver(idx, msg) {
                        resolve(msg);
                    }, reject);
                });
            });
            return Promise;
        });
    });

    /**
     * @module lib/callbacks
     */
    var callbackMap = new WeakMap();
    /**
     * Store a callback for a method or event for a player.
     *
     * @param {Player} player The player object.
     * @param {string} name The method or event name.
     * @param {(function(this:Player, *): void|{resolve: function, reject: function})} callback
     *        The callback to call or an object with resolve and reject functions for a promise.
     * @return {void}
     */

    function storeCallback(player, name, callback) {
        var playerCallbacks = callbackMap.get(player.element) || {};

        if (!(name in playerCallbacks)) {
            playerCallbacks[name] = [];
        }

        playerCallbacks[name].push(callback);
        callbackMap.set(player.element, playerCallbacks);
    }
    /**
     * Get the callbacks for a player and event or method.
     *
     * @param {Player} player The player object.
     * @param {string} name The method or event name
     * @return {function[]}
     */

    function getCallbacks(player, name) {
        var playerCallbacks = callbackMap.get(player.element) || {};
        return playerCallbacks[name] || [];
    }
    /**
     * Remove a stored callback for a method or event for a player.
     *
     * @param {Player} player The player object.
     * @param {string} name The method or event name
     * @param {function} [callback] The specific callback to remove.
     * @return {boolean} Was this the last callback?
     */

    function removeCallback(player, name, callback) {
        var playerCallbacks = callbackMap.get(player.element) || {};

        if (!playerCallbacks[name]) {
            return true;
        } // If no callback is passed, remove all callbacks for the event


        if (!callback) {
            playerCallbacks[name] = [];
            callbackMap.set(player.element, playerCallbacks);
            return true;
        }

        var index = playerCallbacks[name].indexOf(callback);

        if (index !== -1) {
            playerCallbacks[name].splice(index, 1);
        }

        callbackMap.set(player.element, playerCallbacks);
        return playerCallbacks[name] && playerCallbacks[name].length === 0;
    }
    /**
     * Return the first stored callback for a player and event or method.
     *
     * @param {Player} player The player object.
     * @param {string} name The method or event name.
     * @return {function} The callback, or false if there were none
     */

    function shiftCallbacks(player, name) {
        var playerCallbacks = getCallbacks(player, name);

        if (playerCallbacks.length < 1) {
            return false;
        }

        var callback = playerCallbacks.shift();
        removeCallback(player, name, callback);
        return callback;
    }
    /**
     * Move callbacks associated with an element to another element.
     *
     * @param {HTMLElement} oldElement The old element.
     * @param {HTMLElement} newElement The new element.
     * @return {void}
     */

    function swapCallbacks(oldElement, newElement) {
        var playerCallbacks = callbackMap.get(oldElement);
        callbackMap.set(newElement, playerCallbacks);
        callbackMap.delete(oldElement);
    }

    /**
     * @module lib/postmessage
     */
    /**
     * Parse a message received from postMessage.
     *
     * @param {*} data The data received from postMessage.
     * @return {object}
     */

    function parseMessageData(data) {
        if (typeof data === 'string') {
            try {
                data = JSON.parse(data);
            } catch (error) {
                // If the message cannot be parsed, throw the error as a warning
                console.warn(error);
                return {};
            }
        }

        return data;
    }
    /**
     * Post a message to the specified target.
     *
     * @param {Player} player The player object to use.
     * @param {string} method The API method to call.
     * @param {object} params The parameters to send to the player.
     * @return {void}
     */

    function postMessage(player, method, params) {
        if (!player.element.contentWindow || !player.element.contentWindow.postMessage) {
            return;
        }

        var message = {
            method: method
        };

        if (params !== undefined) {
            message.value = params;
        } // IE 8 and 9 do not support passing messages, so stringify them


        var ieVersion = parseFloat(navigator.userAgent.toLowerCase().replace(/^.*msie (\d+).*$/, '$1'));

        if (ieVersion >= 8 && ieVersion < 10) {
            message = JSON.stringify(message);
        }

        player.element.contentWindow.postMessage(message, player.origin);
    }
    /**
     * Parse the data received from a message event.
     *
     * @param {Player} player The player that received the message.
     * @param {(Object|string)} data The message data. Strings will be parsed into JSON.
     * @return {void}
     */

    function processData(player, data) {
        data = parseMessageData(data);
        var callbacks = [];
        var param;

        if (data.event) {
            if (data.event === 'error') {
                var promises = getCallbacks(player, data.data.method);
                promises.forEach(function (promise) {
                    var error = new Error(data.data.message);
                    error.name = data.data.name;
                    promise.reject(error);
                    removeCallback(player, data.data.method, promise);
                });
            }

            callbacks = getCallbacks(player, "event:".concat(data.event));
            param = data.data;
        } else if (data.method) {
            var callback = shiftCallbacks(player, data.method);

            if (callback) {
                callbacks.push(callback);
                param = data.value;
            }
        }

        callbacks.forEach(function (callback) {
            try {
                if (typeof callback === 'function') {
                    callback.call(player, param);
                    return;
                }

                callback.resolve(param);
            } catch (e) {// empty
            }
        });
    }

    /**
     * @module lib/embed
     */
    var oEmbedParameters = ['autopause', 'autoplay', 'background', 'byline', 'color', 'controls', 'dnt', 'height', 'id', 'interactive_params', 'keyboard', 'loop', 'maxheight', 'maxwidth', 'muted', 'playsinline', 'portrait', 'responsive', 'speed', 'texttrack', 'title', 'transparent', 'url', 'width'];
    /**
     * Get the 'data-vimeo'-prefixed attributes from an element as an object.
     *
     * @param {HTMLElement} element The element.
     * @param {Object} [defaults={}] The default values to use.
     * @return {Object<string, string>}
     */

    function getOEmbedParameters(element) {
        var defaults = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return oEmbedParameters.reduce(function (params, param) {
            var value = element.getAttribute("data-vimeo-".concat(param));

            if (value || value === '') {
                params[param] = value === '' ? 1 : value;
            }

            return params;
        }, defaults);
    }
    /**
     * Create an embed from oEmbed data inside an element.
     *
     * @param {object} data The oEmbed data.
     * @param {HTMLElement} element The element to put the iframe in.
     * @return {HTMLIFrameElement} The iframe embed.
     */

    function createEmbed(_ref, element) {
        var html = _ref.html;

        if (!element) {
            throw new TypeError('An element must be provided');
        }

        if (element.getAttribute('data-vimeo-initialized') !== null) {
            return element.querySelector('iframe');
        }

        var div = document.createElement('div');
        div.innerHTML = html;
        element.appendChild(div.firstChild);
        element.setAttribute('data-vimeo-initialized', 'true');
        return element.querySelector('iframe');
    }
    /**
     * Make an oEmbed call for the specified URL.
     *
     * @param {string} videoUrl The vimeo.com url for the video.
     * @param {Object} [params] Parameters to pass to oEmbed.
     * @param {HTMLElement} element The element.
     * @return {Promise}
     */

    function getOEmbedData(videoUrl) {
        var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        var element = arguments.length > 2 ? arguments[2] : undefined;
        return new Promise(function (resolve, reject) {
            if (!isVimeoUrl(videoUrl)) {
                throw new TypeError("\u201C".concat(videoUrl, "\u201D is not a vimeo.com url."));
            }

            var url = "https://vimeo.com/api/oembed.json?url=".concat(encodeURIComponent(videoUrl));

            for (var param in params) {
                if (params.hasOwnProperty(param)) {
                    url += "&".concat(param, "=").concat(encodeURIComponent(params[param]));
                }
            }

            var xhr = 'XDomainRequest' in window ? new XDomainRequest() : new XMLHttpRequest();
            xhr.open('GET', url, true);

            xhr.onload = function () {
                if (xhr.status === 404) {
                    reject(new Error("\u201C".concat(videoUrl, "\u201D was not found.")));
                    return;
                }

                if (xhr.status === 403) {
                    reject(new Error("\u201C".concat(videoUrl, "\u201D is not embeddable.")));
                    return;
                }

                try {
                    var json = JSON.parse(xhr.responseText); // Check api response for 403 on oembed

                    if (json.domain_status_code === 403) {
                        // We still want to create the embed to give users visual feedback
                        createEmbed(json, element);
                        reject(new Error("\u201C".concat(videoUrl, "\u201D is not embeddable.")));
                        return;
                    }

                    resolve(json);
                } catch (error) {
                    reject(error);
                }
            };

            xhr.onerror = function () {
                var status = xhr.status ? " (".concat(xhr.status, ")") : '';
                reject(new Error("There was an error fetching the embed code from Vimeo".concat(status, ".")));
            };

            xhr.send();
        });
    }
    /**
     * Initialize all embeds within a specific element
     *
     * @param {HTMLElement} [parent=document] The parent element.
     * @return {void}
     */

    function initializeEmbeds() {
        var parent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;
        var elements = [].slice.call(parent.querySelectorAll('[data-vimeo-id], [data-vimeo-url]'));

        var handleError = function handleError(error) {
            if ('console' in window && console.error) {
                console.error("There was an error creating an embed: ".concat(error));
            }
        };

        elements.forEach(function (element) {
            try {
                // Skip any that have data-vimeo-defer
                if (element.getAttribute('data-vimeo-defer') !== null) {
                    return;
                }

                var params = getOEmbedParameters(element);
                var url = getVimeoUrl(params);
                getOEmbedData(url, params, element).then(function (data) {
                    return createEmbed(data, element);
                }).catch(handleError);
            } catch (error) {
                handleError(error);
            }
        });
    }
    /**
     * Resize embeds when messaged by the player.
     *
     * @param {HTMLElement} [parent=document] The parent element.
     * @return {void}
     */

    function resizeEmbeds() {
        var parent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;

        // Prevent execution if users include the player.js script multiple times.
        if (window.VimeoPlayerResizeEmbeds_) {
            return;
        }

        window.VimeoPlayerResizeEmbeds_ = true;

        var onMessage = function onMessage(event) {
            if (!isVimeoUrl(event.origin)) {
                return;
            } // 'spacechange' is fired only on embeds with cards


            if (!event.data || event.data.event !== 'spacechange') {
                return;
            }

            var iframes = parent.querySelectorAll('iframe');

            for (var i = 0; i < iframes.length; i++) {
                if (iframes[i].contentWindow !== event.source) {
                    continue;
                } // Change padding-bottom of the enclosing div to accommodate
                // card carousel without distorting aspect ratio


                var space = iframes[i].parentElement;
                space.style.paddingBottom = "".concat(event.data.data[0].bottom, "px");
                break;
            }
        };

        window.addEventListener('message', onMessage);
    }
    /**
     * Add chapters to existing metadata for Google SEO
     *
     * @param {HTMLElement} [parent=document] The parent element.
     * @return {void}
     */

    function initAppendVideoMetadata() {
        var parent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;

        //  Prevent execution if users include the player.js script multiple times.
        if (window.VimeoSeoMetadataAppended) {
            return;
        }

        window.VimeoSeoMetadataAppended = true;

        var onMessage = function onMessage(event) {
            if (!isVimeoUrl(event.origin)) {
                return;
            }

            var data = parseMessageData(event.data);

            if (!data || data.event !== 'ready') {
                return;
            }

            var iframes = parent.querySelectorAll('iframe');

            for (var i = 0; i < iframes.length; i++) {
                var iframe = iframes[i]; // Initiate appendVideoMetadata if iframe is a Vimeo embed

                var isValidMessageSource = iframe.contentWindow === event.source;

                if (isVimeoEmbed(iframe.src) && isValidMessageSource) {
                    var player = new Player(iframe);
                    player.callMethod('appendVideoMetadata', window.location.href);
                }
            }
        };

        window.addEventListener('message', onMessage);
    }
    /**
     * Seek to time indicated by vimeo_t query parameter if present in URL
     *
     * @param {HTMLElement} [parent=document] The parent element.
     * @return {void}
     */

    function checkUrlTimeParam() {
        var parent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document;

        //  Prevent execution if users include the player.js script multiple times.
        if (window.VimeoCheckedUrlTimeParam) {
            return;
        }

        window.VimeoCheckedUrlTimeParam = true;

        var handleError = function handleError(error) {
            if ('console' in window && console.error) {
                console.error("There was an error getting video Id: ".concat(error));
            }
        };

        var onMessage = function onMessage(event) {
            if (!isVimeoUrl(event.origin)) {
                return;
            }

            var data = parseMessageData(event.data);

            if (!data || data.event !== 'ready') {
                return;
            }

            var iframes = parent.querySelectorAll('iframe');

            for (var i = 0; i < iframes.length; i++) {
                var iframe = iframes[i];
                var isValidMessageSource = iframe.contentWindow === event.source;

                if (isVimeoEmbed(iframe.src) && isValidMessageSource) {
                    (function () {
                        var player = new Player(iframe);
                        player.getVideoId().then(function (videoId) {
                            var matches = new RegExp("[?&]vimeo_t_".concat(videoId, "=([^&#]*)")).exec(window.location.href);

                            if (matches && matches[1]) {
                                var sec = decodeURI(matches[1]);
                                player.setCurrentTime(sec);
                            }

                            return;
                        }).catch(handleError);
                    })();
                }
            }
        };

        window.addEventListener('message', onMessage);
    }

    /* MIT License

    Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    Terms */
    function initializeScreenfull() {
        var fn = function () {
            var val;
            var fnMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'], // New WebKit
                ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'], // Old WebKit
                ['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'], ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'], ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];
            var i = 0;
            var l = fnMap.length;
            var ret = {};

            for (; i < l; i++) {
                val = fnMap[i];

                if (val && val[1] in document) {
                    for (i = 0; i < val.length; i++) {
                        ret[fnMap[0][i]] = val[i];
                    }

                    return ret;
                }
            }

            return false;
        }();

        var eventNameMap = {
            fullscreenchange: fn.fullscreenchange,
            fullscreenerror: fn.fullscreenerror
        };
        var screenfull = {
            request: function request(element) {
                return new Promise(function (resolve, reject) {
                    var onFullScreenEntered = function onFullScreenEntered() {
                        screenfull.off('fullscreenchange', onFullScreenEntered);
                        resolve();
                    };

                    screenfull.on('fullscreenchange', onFullScreenEntered);
                    element = element || document.documentElement;
                    var returnPromise = element[fn.requestFullscreen]();

                    if (returnPromise instanceof Promise) {
                        returnPromise.then(onFullScreenEntered).catch(reject);
                    }
                });
            },
            exit: function exit() {
                return new Promise(function (resolve, reject) {
                    if (!screenfull.isFullscreen) {
                        resolve();
                        return;
                    }

                    var onFullScreenExit = function onFullScreenExit() {
                        screenfull.off('fullscreenchange', onFullScreenExit);
                        resolve();
                    };

                    screenfull.on('fullscreenchange', onFullScreenExit);
                    var returnPromise = document[fn.exitFullscreen]();

                    if (returnPromise instanceof Promise) {
                        returnPromise.then(onFullScreenExit).catch(reject);
                    }
                });
            },
            on: function on(event, callback) {
                var eventName = eventNameMap[event];

                if (eventName) {
                    document.addEventListener(eventName, callback);
                }
            },
            off: function off(event, callback) {
                var eventName = eventNameMap[event];

                if (eventName) {
                    document.removeEventListener(eventName, callback);
                }
            }
        };
        Object.defineProperties(screenfull, {
            isFullscreen: {
                get: function get() {
                    return Boolean(document[fn.fullscreenElement]);
                }
            },
            element: {
                enumerable: true,
                get: function get() {
                    return document[fn.fullscreenElement];
                }
            },
            isEnabled: {
                enumerable: true,
                get: function get() {
                    // Coerce to boolean in case of old WebKit
                    return Boolean(document[fn.fullscreenEnabled]);
                }
            }
        });
        return screenfull;
    }

    var playerMap = new WeakMap();
    var readyMap = new WeakMap();
    var screenfull = {};

    var Player = /*#__PURE__*/function () {
        /**
         * Create a Player.
         *
         * @param {(HTMLIFrameElement|HTMLElement|string|jQuery)} element A reference to the Vimeo
         *        player iframe, and id, or a jQuery object.
         * @param {object} [options] oEmbed parameters to use when creating an embed in the element.
         * @return {Player}
         */
        function Player(element) {
            var _this = this;

            var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

            _classCallCheck(this, Player);

            /* global jQuery */
            if (window.jQuery && element instanceof jQuery) {
                if (element.length > 1 && window.console && console.warn) {
                    console.warn('A jQuery object with multiple elements was passed, using the first element.');
                }

                element = element[0];
            } // Find an element by ID


            if (typeof document !== 'undefined' && typeof element === 'string') {
                element = document.getElementById(element);
            } // Not an element!


            if (!isDomElement(element)) {
                throw new TypeError('You must pass either a valid element or a valid id.');
            } // Already initialized an embed in this div, so grab the iframe


            if (element.nodeName !== 'IFRAME') {
                var iframe = element.querySelector('iframe');

                if (iframe) {
                    element = iframe;
                }
            } // iframe url is not a Vimeo url


            if (element.nodeName === 'IFRAME' && !isVimeoUrl(element.getAttribute('src') || '')) {
                throw new Error('The player element passed isn’t a Vimeo embed.');
            } // If there is already a player object in the map, return that


            if (playerMap.has(element)) {
                return playerMap.get(element);
            }

            this._window = element.ownerDocument.defaultView;
            this.element = element;
            this.origin = '*';
            var readyPromise = new npo_src(function (resolve, reject) {
                _this._onMessage = function (event) {
                    if (!isVimeoUrl(event.origin) || _this.element.contentWindow !== event.source) {
                        return;
                    }

                    if (_this.origin === '*') {
                        _this.origin = event.origin;
                    }

                    var data = parseMessageData(event.data);
                    var isError = data && data.event === 'error';
                    var isReadyError = isError && data.data && data.data.method === 'ready';

                    if (isReadyError) {
                        var error = new Error(data.data.message);
                        error.name = data.data.name;
                        reject(error);
                        return;
                    }

                    var isReadyEvent = data && data.event === 'ready';
                    var isPingResponse = data && data.method === 'ping';

                    if (isReadyEvent || isPingResponse) {
                        _this.element.setAttribute('data-ready', 'true');

                        resolve();
                        return;
                    }

                    processData(_this, data);
                };

                _this._window.addEventListener('message', _this._onMessage);

                if (_this.element.nodeName !== 'IFRAME') {
                    var params = getOEmbedParameters(element, options);
                    var url = getVimeoUrl(params);
                    getOEmbedData(url, params, element).then(function (data) {
                        var iframe = createEmbed(data, element); // Overwrite element with the new iframe,
                        // but store reference to the original element

                        _this.element = iframe;
                        _this._originalElement = element;
                        swapCallbacks(element, iframe);
                        playerMap.set(_this.element, _this);
                        return data;
                    }).catch(reject);
                }
            }); // Store a copy of this Player in the map

            readyMap.set(this, readyPromise);
            playerMap.set(this.element, this); // Send a ping to the iframe so the ready promise will be resolved if
            // the player is already ready.

            if (this.element.nodeName === 'IFRAME') {
                postMessage(this, 'ping');
            }

            if (screenfull.isEnabled) {
                var exitFullscreen = function exitFullscreen() {
                    return screenfull.exit();
                };

                this.fullscreenchangeHandler = function () {
                    if (screenfull.isFullscreen) {
                        storeCallback(_this, 'event:exitFullscreen', exitFullscreen);
                    } else {
                        removeCallback(_this, 'event:exitFullscreen', exitFullscreen);
                    } // eslint-disable-next-line


                    _this.ready().then(function () {
                        postMessage(_this, 'fullscreenchange', screenfull.isFullscreen);
                    });
                };

                screenfull.on('fullscreenchange', this.fullscreenchangeHandler);
            }

            return this;
        }
        /**
         * Get a promise for a method.
         *
         * @param {string} name The API method to call.
         * @param {Object} [args={}] Arguments to send via postMessage.
         * @return {Promise}
         */


        _createClass(Player, [{
            key: "callMethod",
            value: function callMethod(name) {
                var _this2 = this;

                var args = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
                return new npo_src(function (resolve, reject) {
                    // We are storing the resolve/reject handlers to call later, so we
                    // can’t return here.
                    // eslint-disable-next-line promise/always-return
                    return _this2.ready().then(function () {
                        storeCallback(_this2, name, {
                            resolve: resolve,
                            reject: reject
                        });
                        postMessage(_this2, name, args);
                    }).catch(reject);
                });
            }
            /**
             * Get a promise for the value of a player property.
             *
             * @param {string} name The property name
             * @return {Promise}
             */

        }, {
            key: "get",
            value: function get(name) {
                var _this3 = this;

                return new npo_src(function (resolve, reject) {
                    name = getMethodName(name, 'get'); // We are storing the resolve/reject handlers to call later, so we
                    // can’t return here.
                    // eslint-disable-next-line promise/always-return

                    return _this3.ready().then(function () {
                        storeCallback(_this3, name, {
                            resolve: resolve,
                            reject: reject
                        });
                        postMessage(_this3, name);
                    }).catch(reject);
                });
            }
            /**
             * Get a promise for setting the value of a player property.
             *
             * @param {string} name The API method to call.
             * @param {mixed} value The value to set.
             * @return {Promise}
             */

        }, {
            key: "set",
            value: function set(name, value) {
                var _this4 = this;

                return new npo_src(function (resolve, reject) {
                    name = getMethodName(name, 'set');

                    if (value === undefined || value === null) {
                        throw new TypeError('There must be a value to set.');
                    } // We are storing the resolve/reject handlers to call later, so we
                    // can’t return here.
                    // eslint-disable-next-line promise/always-return


                    return _this4.ready().then(function () {
                        storeCallback(_this4, name, {
                            resolve: resolve,
                            reject: reject
                        });
                        postMessage(_this4, name, value);
                    }).catch(reject);
                });
            }
            /**
             * Add an event listener for the specified event. Will call the
             * callback with a single parameter, `data`, that contains the data for
             * that event.
             *
             * @param {string} eventName The name of the event.
             * @param {function(*)} callback The function to call when the event fires.
             * @return {void}
             */

        }, {
            key: "on",
            value: function on(eventName, callback) {
                if (!eventName) {
                    throw new TypeError('You must pass an event name.');
                }

                if (!callback) {
                    throw new TypeError('You must pass a callback function.');
                }

                if (typeof callback !== 'function') {
                    throw new TypeError('The callback must be a function.');
                }

                var callbacks = getCallbacks(this, "event:".concat(eventName));

                if (callbacks.length === 0) {
                    this.callMethod('addEventListener', eventName).catch(function () {// Ignore the error. There will be an error event fired that
                        // will trigger the error callback if they are listening.
                    });
                }

                storeCallback(this, "event:".concat(eventName), callback);
            }
            /**
             * Remove an event listener for the specified event. Will remove all
             * listeners for that event if a `callback` isn’t passed, or only that
             * specific callback if it is passed.
             *
             * @param {string} eventName The name of the event.
             * @param {function} [callback] The specific callback to remove.
             * @return {void}
             */

        }, {
            key: "off",
            value: function off(eventName, callback) {
                if (!eventName) {
                    throw new TypeError('You must pass an event name.');
                }

                if (callback && typeof callback !== 'function') {
                    throw new TypeError('The callback must be a function.');
                }

                var lastCallback = removeCallback(this, "event:".concat(eventName), callback); // If there are no callbacks left, remove the listener

                if (lastCallback) {
                    this.callMethod('removeEventListener', eventName).catch(function (e) {// Ignore the error. There will be an error event fired that
                        // will trigger the error callback if they are listening.
                    });
                }
            }
            /**
             * A promise to load a new video.
             *
             * @promise LoadVideoPromise
             * @fulfill {number} The video with this id or url successfully loaded.
             * @reject {TypeError} The id was not a number.
             */

            /**
             * Load a new video into this embed. The promise will be resolved if
             * the video is successfully loaded, or it will be rejected if it could
             * not be loaded.
             *
             * @param {number|string|object} options The id of the video, the url of the video, or an object with embed options.
             * @return {LoadVideoPromise}
             */

        }, {
            key: "loadVideo",
            value: function loadVideo(options) {
                return this.callMethod('loadVideo', options);
            }
            /**
             * A promise to perform an action when the Player is ready.
             *
             * @todo document errors
             * @promise LoadVideoPromise
             * @fulfill {void}
             */

            /**
             * Trigger a function when the player iframe has initialized. You do not
             * need to wait for `ready` to trigger to begin adding event listeners
             * or calling other methods.
             *
             * @return {ReadyPromise}
             */

        }, {
            key: "ready",
            value: function ready() {
                var readyPromise = readyMap.get(this) || new npo_src(function (resolve, reject) {
                    reject(new Error('Unknown player. Probably unloaded.'));
                });
                return npo_src.resolve(readyPromise);
            }
            /**
             * A promise to add a cue point to the player.
             *
             * @promise AddCuePointPromise
             * @fulfill {string} The id of the cue point to use for removeCuePoint.
             * @reject {RangeError} the time was less than 0 or greater than the
             *         video’s duration.
             * @reject {UnsupportedError} Cue points are not supported with the current
             *         player or browser.
             */

            /**
             * Add a cue point to the player.
             *
             * @param {number} time The time for the cue point.
             * @param {object} [data] Arbitrary data to be returned with the cue point.
             * @return {AddCuePointPromise}
             */

        }, {
            key: "addCuePoint",
            value: function addCuePoint(time) {
                var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
                return this.callMethod('addCuePoint', {
                    time: time,
                    data: data
                });
            }
            /**
             * A promise to remove a cue point from the player.
             *
             * @promise AddCuePointPromise
             * @fulfill {string} The id of the cue point that was removed.
             * @reject {InvalidCuePoint} The cue point with the specified id was not
             *         found.
             * @reject {UnsupportedError} Cue points are not supported with the current
             *         player or browser.
             */

            /**
             * Remove a cue point from the video.
             *
             * @param {string} id The id of the cue point to remove.
             * @return {RemoveCuePointPromise}
             */

        }, {
            key: "removeCuePoint",
            value: function removeCuePoint(id) {
                return this.callMethod('removeCuePoint', id);
            }
            /**
             * A representation of a text track on a video.
             *
             * @typedef {Object} VimeoTextTrack
             * @property {string} language The ISO language code.
             * @property {string} kind The kind of track it is (captions or subtitles).
             * @property {string} label The human‐readable label for the track.
             */

            /**
             * A promise to enable a text track.
             *
             * @promise EnableTextTrackPromise
             * @fulfill {VimeoTextTrack} The text track that was enabled.
             * @reject {InvalidTrackLanguageError} No track was available with the
             *         specified language.
             * @reject {InvalidTrackError} No track was available with the specified
             *         language and kind.
             */

            /**
             * Enable the text track with the specified language, and optionally the
             * specified kind (captions or subtitles).
             *
             * When set via the API, the track language will not change the viewer’s
             * stored preference.
             *
             * @param {string} language The two‐letter language code.
             * @param {string} [kind] The kind of track to enable (captions or subtitles).
             * @return {EnableTextTrackPromise}
             */

        }, {
            key: "enableTextTrack",
            value: function enableTextTrack(language, kind) {
                if (!language) {
                    throw new TypeError('You must pass a language.');
                }

                return this.callMethod('enableTextTrack', {
                    language: language,
                    kind: kind
                });
            }
            /**
             * A promise to disable the active text track.
             *
             * @promise DisableTextTrackPromise
             * @fulfill {void} The track was disabled.
             */

            /**
             * Disable the currently-active text track.
             *
             * @return {DisableTextTrackPromise}
             */

        }, {
            key: "disableTextTrack",
            value: function disableTextTrack() {
                return this.callMethod('disableTextTrack');
            }
            /**
             * A promise to pause the video.
             *
             * @promise PausePromise
             * @fulfill {void} The video was paused.
             */

            /**
             * Pause the video if it’s playing.
             *
             * @return {PausePromise}
             */

        }, {
            key: "pause",
            value: function pause() {
                return this.callMethod('pause');
            }
            /**
             * A promise to play the video.
             *
             * @promise PlayPromise
             * @fulfill {void} The video was played.
             */

            /**
             * Play the video if it’s paused. **Note:** on iOS and some other
             * mobile devices, you cannot programmatically trigger play. Once the
             * viewer has tapped on the play button in the player, however, you
             * will be able to use this function.
             *
             * @return {PlayPromise}
             */

        }, {
            key: "play",
            value: function play() {
                return this.callMethod('play');
            }
            /**
             * Request that the player enters fullscreen.
             * @return {Promise}
             */

        }, {
            key: "requestFullscreen",
            value: function requestFullscreen() {
                if (screenfull.isEnabled) {
                    return screenfull.request(this.element);
                }

                return this.callMethod('requestFullscreen');
            }
            /**
             * Request that the player exits fullscreen.
             * @return {Promise}
             */

        }, {
            key: "exitFullscreen",
            value: function exitFullscreen() {
                if (screenfull.isEnabled) {
                    return screenfull.exit();
                }

                return this.callMethod('exitFullscreen');
            }
            /**
             * Returns true if the player is currently fullscreen.
             * @return {Promise}
             */

        }, {
            key: "getFullscreen",
            value: function getFullscreen() {
                if (screenfull.isEnabled) {
                    return npo_src.resolve(screenfull.isFullscreen);
                }

                return this.get('fullscreen');
            }
            /**
             * Request that the player enters picture-in-picture.
             * @return {Promise}
             */

        }, {
            key: "requestPictureInPicture",
            value: function requestPictureInPicture() {
                return this.callMethod('requestPictureInPicture');
            }
            /**
             * Request that the player exits picture-in-picture.
             * @return {Promise}
             */

        }, {
            key: "exitPictureInPicture",
            value: function exitPictureInPicture() {
                return this.callMethod('exitPictureInPicture');
            }
            /**
             * Returns true if the player is currently picture-in-picture.
             * @return {Promise}
             */

        }, {
            key: "getPictureInPicture",
            value: function getPictureInPicture() {
                return this.get('pictureInPicture');
            }
            /**
             * A promise to unload the video.
             *
             * @promise UnloadPromise
             * @fulfill {void} The video was unloaded.
             */

            /**
             * Return the player to its initial state.
             *
             * @return {UnloadPromise}
             */

        }, {
            key: "unload",
            value: function unload() {
                return this.callMethod('unload');
            }
            /**
             * Cleanup the player and remove it from the DOM
             *
             * It won't be usable and a new one should be constructed
             *  in order to do any operations.
             *
             * @return {Promise}
             */

        }, {
            key: "destroy",
            value: function destroy() {
                var _this5 = this;

                return new npo_src(function (resolve) {
                    readyMap.delete(_this5);
                    playerMap.delete(_this5.element);

                    if (_this5._originalElement) {
                        playerMap.delete(_this5._originalElement);

                        _this5._originalElement.removeAttribute('data-vimeo-initialized');
                    }

                    if (_this5.element && _this5.element.nodeName === 'IFRAME' && _this5.element.parentNode) {
                        // If we've added an additional wrapper div, remove that from the DOM.
                        // If not, just remove the iframe element.
                        if (_this5.element.parentNode.parentNode && _this5._originalElement && _this5._originalElement !== _this5.element.parentNode) {
                            _this5.element.parentNode.parentNode.removeChild(_this5.element.parentNode);
                        } else {
                            _this5.element.parentNode.removeChild(_this5.element);
                        }
                    } // If the clip is private there is a case where the element stays the
                    // div element. Destroy should reset the div and remove the iframe child.


                    if (_this5.element && _this5.element.nodeName === 'DIV' && _this5.element.parentNode) {
                        _this5.element.removeAttribute('data-vimeo-initialized');

                        var iframe = _this5.element.querySelector('iframe');

                        if (iframe && iframe.parentNode) {
                            // If we've added an additional wrapper div, remove that from the DOM.
                            // If not, just remove the iframe element.
                            if (iframe.parentNode.parentNode && _this5._originalElement && _this5._originalElement !== iframe.parentNode) {
                                iframe.parentNode.parentNode.removeChild(iframe.parentNode);
                            } else {
                                iframe.parentNode.removeChild(iframe);
                            }
                        }
                    }

                    _this5._window.removeEventListener('message', _this5._onMessage);

                    if (screenfull.isEnabled) {
                        screenfull.off('fullscreenchange', _this5.fullscreenchangeHandler);
                    }

                    resolve();
                });
            }
            /**
             * A promise to get the autopause behavior of the video.
             *
             * @promise GetAutopausePromise
             * @fulfill {boolean} Whether autopause is turned on or off.
             * @reject {UnsupportedError} Autopause is not supported with the current
             *         player or browser.
             */

            /**
             * Get the autopause behavior for this player.
             *
             * @return {GetAutopausePromise}
             */

        }, {
            key: "getAutopause",
            value: function getAutopause() {
                return this.get('autopause');
            }
            /**
             * A promise to set the autopause behavior of the video.
             *
             * @promise SetAutopausePromise
             * @fulfill {boolean} Whether autopause is turned on or off.
             * @reject {UnsupportedError} Autopause is not supported with the current
             *         player or browser.
             */

            /**
             * Enable or disable the autopause behavior of this player.
             *
             * By default, when another video is played in the same browser, this
             * player will automatically pause. Unless you have a specific reason
             * for doing so, we recommend that you leave autopause set to the
             * default (`true`).
             *
             * @param {boolean} autopause
             * @return {SetAutopausePromise}
             */

        }, {
            key: "setAutopause",
            value: function setAutopause(autopause) {
                return this.set('autopause', autopause);
            }
            /**
             * A promise to get the buffered property of the video.
             *
             * @promise GetBufferedPromise
             * @fulfill {Array} Buffered Timeranges converted to an Array.
             */

            /**
             * Get the buffered property of the video.
             *
             * @return {GetBufferedPromise}
             */

        }, {
            key: "getBuffered",
            value: function getBuffered() {
                return this.get('buffered');
            }
            /**
             * @typedef {Object} CameraProperties
             * @prop {number} props.yaw - Number between 0 and 360.
             * @prop {number} props.pitch - Number between -90 and 90.
             * @prop {number} props.roll - Number between -180 and 180.
             * @prop {number} props.fov - The field of view in degrees.
             */

            /**
             * A promise to get the camera properties of the player.
             *
             * @promise GetCameraPromise
             * @fulfill {CameraProperties} The camera properties.
             */

            /**
             * For 360° videos get the camera properties for this player.
             *
             * @return {GetCameraPromise}
             */

        }, {
            key: "getCameraProps",
            value: function getCameraProps() {
                return this.get('cameraProps');
            }
            /**
             * A promise to set the camera properties of the player.
             *
             * @promise SetCameraPromise
             * @fulfill {Object} The camera was successfully set.
             * @reject {RangeError} The range was out of bounds.
             */

            /**
             * For 360° videos set the camera properties for this player.
             *
             * @param {CameraProperties} camera The camera properties
             * @return {SetCameraPromise}
             */

        }, {
            key: "setCameraProps",
            value: function setCameraProps(camera) {
                return this.set('cameraProps', camera);
            }
            /**
             * A representation of a chapter.
             *
             * @typedef {Object} VimeoChapter
             * @property {number} startTime The start time of the chapter.
             * @property {object} title The title of the chapter.
             * @property {number} index The place in the order of Chapters. Starts at 1.
             */

            /**
             * A promise to get chapters for the video.
             *
             * @promise GetChaptersPromise
             * @fulfill {VimeoChapter[]} The chapters for the video.
             */

            /**
             * Get an array of all the chapters for the video.
             *
             * @return {GetChaptersPromise}
             */

        }, {
            key: "getChapters",
            value: function getChapters() {
                return this.get('chapters');
            }
            /**
             * A promise to get the currently active chapter.
             *
             * @promise GetCurrentChaptersPromise
             * @fulfill {VimeoChapter|undefined} The current chapter for the video.
             */

            /**
             * Get the currently active chapter for the video.
             *
             * @return {GetCurrentChaptersPromise}
             */

        }, {
            key: "getCurrentChapter",
            value: function getCurrentChapter() {
                return this.get('currentChapter');
            }
            /**
             * A promise to get the color of the player.
             *
             * @promise GetColorPromise
             * @fulfill {string} The hex color of the player.
             */

            /**
             * Get the color for this player.
             *
             * @return {GetColorPromise}
             */

        }, {
            key: "getColor",
            value: function getColor() {
                return this.get('color');
            }
            /**
             * A promise to set the color of the player.
             *
             * @promise SetColorPromise
             * @fulfill {string} The color was successfully set.
             * @reject {TypeError} The string was not a valid hex or rgb color.
             * @reject {ContrastError} The color was set, but the contrast is
             *         outside of the acceptable range.
             * @reject {EmbedSettingsError} The owner of the player has chosen to
             *         use a specific color.
             */

            /**
             * Set the color of this player to a hex or rgb string. Setting the
             * color may fail if the owner of the video has set their embed
             * preferences to force a specific color.
             *
             * @param {string} color The hex or rgb color string to set.
             * @return {SetColorPromise}
             */

        }, {
            key: "setColor",
            value: function setColor(color) {
                return this.set('color', color);
            }
            /**
             * A representation of a cue point.
             *
             * @typedef {Object} VimeoCuePoint
             * @property {number} time The time of the cue point.
             * @property {object} data The data passed when adding the cue point.
             * @property {string} id The unique id for use with removeCuePoint.
             */

            /**
             * A promise to get the cue points of a video.
             *
             * @promise GetCuePointsPromise
             * @fulfill {VimeoCuePoint[]} The cue points added to the video.
             * @reject {UnsupportedError} Cue points are not supported with the current
             *         player or browser.
             */

            /**
             * Get an array of the cue points added to the video.
             *
             * @return {GetCuePointsPromise}
             */

        }, {
            key: "getCuePoints",
            value: function getCuePoints() {
                return this.get('cuePoints');
            }
            /**
             * A promise to get the current time of the video.
             *
             * @promise GetCurrentTimePromise
             * @fulfill {number} The current time in seconds.
             */

            /**
             * Get the current playback position in seconds.
             *
             * @return {GetCurrentTimePromise}
             */

        }, {
            key: "getCurrentTime",
            value: function getCurrentTime() {
                return this.get('currentTime');
            }
            /**
             * A promise to set the current time of the video.
             *
             * @promise SetCurrentTimePromise
             * @fulfill {number} The actual current time that was set.
             * @reject {RangeError} the time was less than 0 or greater than the
             *         video’s duration.
             */

            /**
             * Set the current playback position in seconds. If the player was
             * paused, it will remain paused. Likewise, if the player was playing,
             * it will resume playing once the video has buffered.
             *
             * You can provide an accurate time and the player will attempt to seek
             * to as close to that time as possible. The exact time will be the
             * fulfilled value of the promise.
             *
             * @param {number} currentTime
             * @return {SetCurrentTimePromise}
             */

        }, {
            key: "setCurrentTime",
            value: function setCurrentTime(currentTime) {
                return this.set('currentTime', currentTime);
            }
            /**
             * A promise to get the duration of the video.
             *
             * @promise GetDurationPromise
             * @fulfill {number} The duration in seconds.
             */

            /**
             * Get the duration of the video in seconds. It will be rounded to the
             * nearest second before playback begins, and to the nearest thousandth
             * of a second after playback begins.
             *
             * @return {GetDurationPromise}
             */

        }, {
            key: "getDuration",
            value: function getDuration() {
                return this.get('duration');
            }
            /**
             * A promise to get the ended state of the video.
             *
             * @promise GetEndedPromise
             * @fulfill {boolean} Whether or not the video has ended.
             */

            /**
             * Get the ended state of the video. The video has ended if
             * `currentTime === duration`.
             *
             * @return {GetEndedPromise}
             */

        }, {
            key: "getEnded",
            value: function getEnded() {
                return this.get('ended');
            }
            /**
             * A promise to get the loop state of the player.
             *
             * @promise GetLoopPromise
             * @fulfill {boolean} Whether or not the player is set to loop.
             */

            /**
             * Get the loop state of the player.
             *
             * @return {GetLoopPromise}
             */

        }, {
            key: "getLoop",
            value: function getLoop() {
                return this.get('loop');
            }
            /**
             * A promise to set the loop state of the player.
             *
             * @promise SetLoopPromise
             * @fulfill {boolean} The loop state that was set.
             */

            /**
             * Set the loop state of the player. When set to `true`, the player
             * will start over immediately once playback ends.
             *
             * @param {boolean} loop
             * @return {SetLoopPromise}
             */

        }, {
            key: "setLoop",
            value: function setLoop(loop) {
                return this.set('loop', loop);
            }
            /**
             * A promise to set the muted state of the player.
             *
             * @promise SetMutedPromise
             * @fulfill {boolean} The muted state that was set.
             */

            /**
             * Set the muted state of the player. When set to `true`, the player
             * volume will be muted.
             *
             * @param {boolean} muted
             * @return {SetMutedPromise}
             */

        }, {
            key: "setMuted",
            value: function setMuted(muted) {
                return this.set('muted', muted);
            }
            /**
             * A promise to get the muted state of the player.
             *
             * @promise GetMutedPromise
             * @fulfill {boolean} Whether or not the player is muted.
             */

            /**
             * Get the muted state of the player.
             *
             * @return {GetMutedPromise}
             */

        }, {
            key: "getMuted",
            value: function getMuted() {
                return this.get('muted');
            }
            /**
             * A promise to get the paused state of the player.
             *
             * @promise GetLoopPromise
             * @fulfill {boolean} Whether or not the video is paused.
             */

            /**
             * Get the paused state of the player.
             *
             * @return {GetLoopPromise}
             */

        }, {
            key: "getPaused",
            value: function getPaused() {
                return this.get('paused');
            }
            /**
             * A promise to get the playback rate of the player.
             *
             * @promise GetPlaybackRatePromise
             * @fulfill {number} The playback rate of the player on a scale from 0.5 to 2.
             */

            /**
             * Get the playback rate of the player on a scale from `0.5` to `2`.
             *
             * @return {GetPlaybackRatePromise}
             */

        }, {
            key: "getPlaybackRate",
            value: function getPlaybackRate() {
                return this.get('playbackRate');
            }
            /**
             * A promise to set the playbackrate of the player.
             *
             * @promise SetPlaybackRatePromise
             * @fulfill {number} The playback rate was set.
             * @reject {RangeError} The playback rate was less than 0.5 or greater than 2.
             */

            /**
             * Set the playback rate of the player on a scale from `0.5` to `2`. When set
             * via the API, the playback rate will not be synchronized to other
             * players or stored as the viewer's preference.
             *
             * @param {number} playbackRate
             * @return {SetPlaybackRatePromise}
             */

        }, {
            key: "setPlaybackRate",
            value: function setPlaybackRate(playbackRate) {
                return this.set('playbackRate', playbackRate);
            }
            /**
             * A promise to get the played property of the video.
             *
             * @promise GetPlayedPromise
             * @fulfill {Array} Played Timeranges converted to an Array.
             */

            /**
             * Get the played property of the video.
             *
             * @return {GetPlayedPromise}
             */

        }, {
            key: "getPlayed",
            value: function getPlayed() {
                return this.get('played');
            }
            /**
             * A promise to get the qualities available of the current video.
             *
             * @promise GetQualitiesPromise
             * @fulfill {Array} The qualities of the video.
             */

            /**
             * Get the qualities of the current video.
             *
             * @return {GetQualitiesPromise}
             */

        }, {
            key: "getQualities",
            value: function getQualities() {
                return this.get('qualities');
            }
            /**
             * A promise to get the current set quality of the video.
             *
             * @promise GetQualityPromise
             * @fulfill {string} The current set quality.
             */

            /**
             * Get the current set quality of the video.
             *
             * @return {GetQualityPromise}
             */

        }, {
            key: "getQuality",
            value: function getQuality() {
                return this.get('quality');
            }
            /**
             * A promise to set the video quality.
             *
             * @promise SetQualityPromise
             * @fulfill {number} The quality was set.
             * @reject {RangeError} The quality is not available.
             */

            /**
             * Set a video quality.
             *
             * @param {string} quality
             * @return {SetQualityPromise}
             */

        }, {
            key: "setQuality",
            value: function setQuality(quality) {
                return this.set('quality', quality);
            }
            /**
             * A promise to get the seekable property of the video.
             *
             * @promise GetSeekablePromise
             * @fulfill {Array} Seekable Timeranges converted to an Array.
             */

            /**
             * Get the seekable property of the video.
             *
             * @return {GetSeekablePromise}
             */

        }, {
            key: "getSeekable",
            value: function getSeekable() {
                return this.get('seekable');
            }
            /**
             * A promise to get the seeking property of the player.
             *
             * @promise GetSeekingPromise
             * @fulfill {boolean} Whether or not the player is currently seeking.
             */

            /**
             * Get if the player is currently seeking.
             *
             * @return {GetSeekingPromise}
             */

        }, {
            key: "getSeeking",
            value: function getSeeking() {
                return this.get('seeking');
            }
            /**
             * A promise to get the text tracks of a video.
             *
             * @promise GetTextTracksPromise
             * @fulfill {VimeoTextTrack[]} The text tracks associated with the video.
             */

            /**
             * Get an array of the text tracks that exist for the video.
             *
             * @return {GetTextTracksPromise}
             */

        }, {
            key: "getTextTracks",
            value: function getTextTracks() {
                return this.get('textTracks');
            }
            /**
             * A promise to get the embed code for the video.
             *
             * @promise GetVideoEmbedCodePromise
             * @fulfill {string} The `<iframe>` embed code for the video.
             */

            /**
             * Get the `<iframe>` embed code for the video.
             *
             * @return {GetVideoEmbedCodePromise}
             */

        }, {
            key: "getVideoEmbedCode",
            value: function getVideoEmbedCode() {
                return this.get('videoEmbedCode');
            }
            /**
             * A promise to get the id of the video.
             *
             * @promise GetVideoIdPromise
             * @fulfill {number} The id of the video.
             */

            /**
             * Get the id of the video.
             *
             * @return {GetVideoIdPromise}
             */

        }, {
            key: "getVideoId",
            value: function getVideoId() {
                return this.get('videoId');
            }
            /**
             * A promise to get the title of the video.
             *
             * @promise GetVideoTitlePromise
             * @fulfill {number} The title of the video.
             */

            /**
             * Get the title of the video.
             *
             * @return {GetVideoTitlePromise}
             */

        }, {
            key: "getVideoTitle",
            value: function getVideoTitle() {
                return this.get('videoTitle');
            }
            /**
             * A promise to get the native width of the video.
             *
             * @promise GetVideoWidthPromise
             * @fulfill {number} The native width of the video.
             */

            /**
             * Get the native width of the currently‐playing video. The width of
             * the highest‐resolution available will be used before playback begins.
             *
             * @return {GetVideoWidthPromise}
             */

        }, {
            key: "getVideoWidth",
            value: function getVideoWidth() {
                return this.get('videoWidth');
            }
            /**
             * A promise to get the native height of the video.
             *
             * @promise GetVideoHeightPromise
             * @fulfill {number} The native height of the video.
             */

            /**
             * Get the native height of the currently‐playing video. The height of
             * the highest‐resolution available will be used before playback begins.
             *
             * @return {GetVideoHeightPromise}
             */

        }, {
            key: "getVideoHeight",
            value: function getVideoHeight() {
                return this.get('videoHeight');
            }
            /**
             * A promise to get the vimeo.com url for the video.
             *
             * @promise GetVideoUrlPromise
             * @fulfill {number} The vimeo.com url for the video.
             * @reject {PrivacyError} The url isn’t available because of the video’s privacy setting.
             */

            /**
             * Get the vimeo.com url for the video.
             *
             * @return {GetVideoUrlPromise}
             */

        }, {
            key: "getVideoUrl",
            value: function getVideoUrl() {
                return this.get('videoUrl');
            }
            /**
             * A promise to get the volume level of the player.
             *
             * @promise GetVolumePromise
             * @fulfill {number} The volume level of the player on a scale from 0 to 1.
             */

            /**
             * Get the current volume level of the player on a scale from `0` to `1`.
             *
             * Most mobile devices do not support an independent volume from the
             * system volume. In those cases, this method will always return `1`.
             *
             * @return {GetVolumePromise}
             */

        }, {
            key: "getVolume",
            value: function getVolume() {
                return this.get('volume');
            }
            /**
             * A promise to set the volume level of the player.
             *
             * @promise SetVolumePromise
             * @fulfill {number} The volume was set.
             * @reject {RangeError} The volume was less than 0 or greater than 1.
             */

            /**
             * Set the volume of the player on a scale from `0` to `1`. When set
             * via the API, the volume level will not be synchronized to other
             * players or stored as the viewer’s preference.
             *
             * Most mobile devices do not support setting the volume. An error will
             * *not* be triggered in that situation.
             *
             * @param {number} volume
             * @return {SetVolumePromise}
             */

        }, {
            key: "setVolume",
            value: function setVolume(volume) {
                return this.set('volume', volume);
            }
        }]);

        return Player;
    }(); // Setup embed only if this is not a node environment


    if (!isNode) {
        screenfull = initializeScreenfull();
        initializeEmbeds();
        resizeEmbeds();
        initAppendVideoMetadata();
        checkUrlTimeParam();
    }

    return Player;

})));

//# sourceMappingURL=player.js.map
/*!
 * Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
 */

/*!
 * Bootstrap v3.4.1 (https://getbootstrap.com/)
 * Copyright 2011-2024 Twitter, Inc.
 * Licensed under the MIT license
 */

if (typeof jQuery === 'undefined') {
  throw new Error('Bootstrap\'s JavaScript requires jQuery')
}
+function ($) {
  'use strict';
  var version = $.fn.jquery.split(' ')[0].split('.')
  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {
    throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')
  }
}(jQuery);

/* ========================================================================
 * Bootstrap: tooltip.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#tooltip
 * Inspired by the original jQuery.tipsy by Jason Frame
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */

+function ($) {
  'use strict';

  var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']

  var uriAttrs = [
    'background',
    'cite',
    'href',
    'itemtype',
    'longdesc',
    'poster',
    'src',
    'xlink:href'
  ]

  var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i

  var DefaultWhitelist = {
    // Global attributes allowed on any supplied element below.
    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
    a: ['target', 'href', 'title', 'rel'],
    area: [],
    b: [],
    br: [],
    col: [],
    code: [],
    div: [],
    em: [],
    hr: [],
    h1: [],
    h2: [],
    h3: [],
    h4: [],
    h5: [],
    h6: [],
    i: [],
    img: ['src', 'alt', 'title', 'width', 'height'],
    li: [],
    ol: [],
    p: [],
    pre: [],
    s: [],
    small: [],
    span: [],
    sub: [],
    sup: [],
    strong: [],
    u: [],
    ul: []
  }

  /**
   * A pattern that recognizes a commonly useful subset of URLs that are safe.
   *
   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
   */
  var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi

  /**
   * A pattern that matches safe data URLs. Only matches image, video and audio types.
   *
   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
   */
  var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i

  function allowedAttribute(attr, allowedAttributeList) {
    var attrName = attr.nodeName.toLowerCase()

    if ($.inArray(attrName, allowedAttributeList) !== -1) {
      if ($.inArray(attrName, uriAttrs) !== -1) {
        return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))
      }

      return true
    }

    var regExp = $(allowedAttributeList).filter(function (index, value) {
      return value instanceof RegExp
    })

    // Check if a regular expression validates the attribute.
    for (var i = 0, l = regExp.length; i < l; i++) {
      if (attrName.match(regExp[i])) {
        return true
      }
    }

    return false
  }

  function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
    if (unsafeHtml.length === 0) {
      return unsafeHtml
    }

    if (sanitizeFn && typeof sanitizeFn === 'function') {
      return sanitizeFn(unsafeHtml)
    }

    // IE 8 and below don't support createHTMLDocument
    if (!document.implementation || !document.implementation.createHTMLDocument) {
      return unsafeHtml
    }

    var createdDocument = document.implementation.createHTMLDocument('sanitization')
    createdDocument.body.innerHTML = unsafeHtml

    var whitelistKeys = $.map(whiteList, function (el, i) { return i })
    var elements = $(createdDocument.body).find('*')

    for (var i = 0, len = elements.length; i < len; i++) {
      var el = elements[i]
      var elName = el.nodeName.toLowerCase()

      if ($.inArray(elName, whitelistKeys) === -1) {
        el.parentNode.removeChild(el)

        continue
      }

      var attributeList = $.map(el.attributes, function (el) { return el })
      var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])

      for (var j = 0, len2 = attributeList.length; j < len2; j++) {
        if (!allowedAttribute(attributeList[j], whitelistedAttributes)) {
          el.removeAttribute(attributeList[j].nodeName)
        }
      }
    }

    return createdDocument.body.innerHTML
  }

  // TOOLTIP PUBLIC CLASS DEFINITION
  // ===============================

  var Tooltip = function (element, options) {
    this.type       = null
    this.options    = null
    this.enabled    = null
    this.timeout    = null
    this.hoverState = null
    this.$element   = null
    this.inState    = null

    this.init('tooltip', element, options)
  }

  Tooltip.VERSION  = '3.4.1'

  Tooltip.TRANSITION_DURATION = 150

  Tooltip.DEFAULTS = {
    animation: true,
    placement: 'top',
    selector: false,
    template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
    trigger: 'hover focus',
    title: '',
    delay: 0,
    html: false,
    container: false,
    viewport: {
      selector: 'body',
      padding: 0
    },
    sanitize : true,
    sanitizeFn : null,
    whiteList : DefaultWhitelist
  }

  Tooltip.prototype.init = function (type, element, options) {
    this.enabled   = true
    this.type      = type
    this.$element  = $(element)
    this.options   = this.getOptions(options)
    this.$viewport = this.options.viewport && $(document).find(
        typeof this.options.viewport === 'function'
            ? this.options.viewport.call(this, this.$element)
            : (this.options.viewport.selector || this.options.viewport)
    );
    this.inState   = { click: false, hover: false, focus: false }

    if (this.$element[0] instanceof document.constructor && !this.options.selector) {
      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
    }

    var triggers = this.options.trigger.split(' ')

    for (var i = triggers.length; i--;) {
      var trigger = triggers[i]

      if (trigger == 'click') {
        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
      } else if (trigger != 'manual') {
        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'

        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
      }
    }

    this.options.selector ?
        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
        this.fixTitle()
  }

  Tooltip.prototype.getDefaults = function () {
    return Tooltip.DEFAULTS
  }

  Tooltip.prototype.getOptions = function (options) {
    var dataAttributes = this.$element.data()

    for (var dataAttr in dataAttributes) {
      if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {
        delete dataAttributes[dataAttr]
      }
    }

    options = $.extend({}, this.getDefaults(), dataAttributes, options)

    if (options.delay && typeof options.delay == 'number') {
      options.delay = {
        show: options.delay,
        hide: options.delay
      }
    }

    if (options.sanitize) {
      options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn)
    }

    return options
  }

  Tooltip.prototype.getDelegateOptions = function () {
    var options  = {}
    var defaults = this.getDefaults()

    this._options && $.each(this._options, function (key, value) {
      if (defaults[key] != value) options[key] = value
    })

    return options
  }

  Tooltip.prototype.enter = function (obj) {
    var self = obj instanceof this.constructor ?
        obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    if (obj instanceof $.Event) {
      self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
    }

    if (self.tip().hasClass('in') || self.hoverState == 'in') {
      self.hoverState = 'in'
      return
    }

    clearTimeout(self.timeout)

    self.hoverState = 'in'

    if (!self.options.delay || !self.options.delay.show) return self.show()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'in') self.show()
    }, self.options.delay.show)
  }

  Tooltip.prototype.isInStateTrue = function () {
    for (var key in this.inState) {
      if (this.inState[key]) return true
    }

    return false
  }

  Tooltip.prototype.leave = function (obj) {
    var self = obj instanceof this.constructor ?
        obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    if (obj instanceof $.Event) {
      self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
    }

    if (self.isInStateTrue()) return

    clearTimeout(self.timeout)

    self.hoverState = 'out'

    if (!self.options.delay || !self.options.delay.hide) return self.hide()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'out') self.hide()
    }, self.options.delay.hide)
  }

  Tooltip.prototype.show = function () {
    var e = $.Event('show.bs.' + this.type)

    if (this.hasContent() && this.enabled) {
      this.$element.trigger(e)

      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
      if (e.isDefaultPrevented() || !inDom) return
      var that = this

      var $tip = this.tip()

      var tipId = this.getUID(this.type)

      this.setContent()
      $tip.attr('id', tipId)
      this.$element.attr('aria-describedby', tipId)

      if (this.options.animation) $tip.addClass('fade')

      var placement = typeof this.options.placement == 'function' ?
          this.options.placement.call(this, $tip[0], this.$element[0]) :
          this.options.placement

      var autoToken = /\s?auto?\s?/i
      var autoPlace = autoToken.test(placement)
      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'

      $tip
          .detach()
          .css({ top: 0, left: 0, display: 'block' })
          .addClass(placement)
          .data('bs.' + this.type, this)

      this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)
      this.$element.trigger('inserted.bs.' + this.type)

      var pos          = this.getPosition()
      var actualWidth  = $tip[0].offsetWidth
      var actualHeight = $tip[0].offsetHeight

      if (autoPlace) {
        var orgPlacement = placement
        var viewportDim = this.getPosition(this.$viewport)

        placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :
            placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :
                placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :
                    placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :
                        placement

        $tip
            .removeClass(orgPlacement)
            .addClass(placement)
      }

      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)

      this.applyPlacement(calculatedOffset, placement)

      var complete = function () {
        var prevHoverState = that.hoverState
        that.$element.trigger('shown.bs.' + that.type)
        that.hoverState = null

        if (prevHoverState == 'out') that.leave(that)
      }

      $.support.transition && this.$tip.hasClass('fade') ?
          $tip
              .one('bsTransitionEnd', complete)
              .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
          complete()
    }
  }

  Tooltip.prototype.applyPlacement = function (offset, placement) {
    var $tip   = this.tip()
    var width  = $tip[0].offsetWidth
    var height = $tip[0].offsetHeight

    // manually read margins because getBoundingClientRect includes difference
    var marginTop = parseInt($tip.css('margin-top'), 10)
    var marginLeft = parseInt($tip.css('margin-left'), 10)

    // we must check for NaN for ie 8/9
    if (isNaN(marginTop))  marginTop  = 0
    if (isNaN(marginLeft)) marginLeft = 0

    offset.top  += marginTop
    offset.left += marginLeft

    // $.fn.offset doesn't round pixel values
    // so we use setOffset directly with our own function B-0
    $.offset.setOffset($tip[0], $.extend({
      using: function (props) {
        $tip.css({
          top: Math.round(props.top),
          left: Math.round(props.left)
        })
      }
    }, offset), 0)

    $tip.addClass('in')

    // check to see if placing tip in new offset caused the tip to resize itself
    var actualWidth  = $tip[0].offsetWidth
    var actualHeight = $tip[0].offsetHeight

    if (placement == 'top' && actualHeight != height) {
      offset.top = offset.top + height - actualHeight
    }

    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)

    if (delta.left) offset.left += delta.left
    else offset.top += delta.top

    var isVertical          = /top|bottom/.test(placement)
    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'

    $tip.offset(offset)
    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
  }

  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
    this.arrow()
        .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
        .css(isVertical ? 'top' : 'left', '')
  }

  Tooltip.prototype.setContent = function () {
    var $tip  = this.tip()
    var title = this.getTitle()

    if (this.options.html) {
      if (this.options.sanitize) {
        title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn)
      }

      $tip.find('.tooltip-inner').html(title)
    } else {
      $tip.find('.tooltip-inner').text(title)
    }

    $tip.removeClass('fade in top bottom left right')
  }

  Tooltip.prototype.hide = function (callback) {
    var that = this
    var $tip = $(this.$tip)
    var e    = $.Event('hide.bs.' + this.type)

    function complete() {
      if (that.hoverState != 'in') $tip.detach()
      if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
        that.$element
            .removeAttr('aria-describedby')
            .trigger('hidden.bs.' + that.type)
      }
      callback && callback()
    }

    this.$element.trigger(e)

    if (e.isDefaultPrevented()) return

    $tip.removeClass('in')

    $.support.transition && $tip.hasClass('fade') ?
        $tip
            .one('bsTransitionEnd', complete)
            .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
        complete()

    this.hoverState = null

    return this
  }

  Tooltip.prototype.fixTitle = function () {
    var $e = this.$element
    if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
    }
  }

  Tooltip.prototype.hasContent = function () {
    return this.getTitle()
  }

  Tooltip.prototype.getPosition = function ($element) {
    $element   = $element || this.$element

    var el     = $element[0]
    var isBody = el.tagName == 'BODY'

    var elRect    = el.getBoundingClientRect()
    if (elRect.width == null) {
      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
    }
    var isSvg = window.SVGElement && el instanceof window.SVGElement
    // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
    // See https://github.com/twbs/bootstrap/issues/20280
    var elOffset  = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null

    return $.extend({}, elRect, scroll, outerDims, elOffset)
  }

  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
        placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
            placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
                /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }

  }

  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
    var delta = { top: 0, left: 0 }
    if (!this.$viewport) return delta

    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
    var viewportDimensions = this.getPosition(this.$viewport)

    if (/right|left/.test(placement)) {
      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
      if (topEdgeOffset < viewportDimensions.top) { // top overflow
        delta.top = viewportDimensions.top - topEdgeOffset
      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
      }
    } else {
      var leftEdgeOffset  = pos.left - viewportPadding
      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
        delta.left = viewportDimensions.left - leftEdgeOffset
      } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
      }
    }

    return delta
  }

  Tooltip.prototype.getTitle = function () {
    var title
    var $e = this.$element
    var o  = this.options

    title = $e.attr('data-original-title')
        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

    return title
  }

  Tooltip.prototype.getUID = function (prefix) {
    do prefix += ~~(Math.random() * 1000000)
    while (document.getElementById(prefix))
    return prefix
  }

  Tooltip.prototype.tip = function () {
    if (!this.$tip) {
      this.$tip = $(this.options.template)
      if (this.$tip.length != 1) {
        throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
      }
    }
    return this.$tip
  }

  Tooltip.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
  }

  Tooltip.prototype.enable = function () {
    this.enabled = true
  }

  Tooltip.prototype.disable = function () {
    this.enabled = false
  }

  Tooltip.prototype.toggleEnabled = function () {
    this.enabled = !this.enabled
  }

  Tooltip.prototype.toggle = function (e) {
    var self = this
    if (e) {
      self = $(e.currentTarget).data('bs.' + this.type)
      if (!self) {
        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
        $(e.currentTarget).data('bs.' + this.type, self)
      }
    }

    if (e) {
      self.inState.click = !self.inState.click
      if (self.isInStateTrue()) self.enter(self)
      else self.leave(self)
    } else {
      self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
    }
  }

  Tooltip.prototype.destroy = function () {
    var that = this
    clearTimeout(this.timeout)
    this.hide(function () {
      that.$element.off('.' + that.type).removeData('bs.' + that.type)
      if (that.$tip) {
        that.$tip.detach()
      }
      that.$tip = null
      that.$arrow = null
      that.$viewport = null
      that.$element = null
    })
  }

  Tooltip.prototype.sanitizeHtml = function (unsafeHtml) {
    return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn)
  }

  // TOOLTIP PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.tooltip')
      var options = typeof option == 'object' && option

      if (!data && /destroy|hide/.test(option)) return
      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.tooltip

  $.fn.tooltip             = Plugin
  $.fn.tooltip.Constructor = Tooltip


  // TOOLTIP NO CONFLICT
  // ===================

  $.fn.tooltip.noConflict = function () {
    $.fn.tooltip = old
    return this
  }

}(jQuery);

/* ========================================================================
 * Bootstrap: popover.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#popovers
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // POPOVER PUBLIC CLASS DEFINITION
  // ===============================

  var Popover = function (element, options) {
    this.init('popover', element, options)
  }

  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')

  Popover.VERSION  = '3.4.1'

  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
    placement: 'right',
    trigger: 'click',
    content: '',
    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
  })


  // NOTE: POPOVER EXTENDS tooltip.js
  // ================================

  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)

  Popover.prototype.constructor = Popover

  Popover.prototype.getDefaults = function () {
    return Popover.DEFAULTS
  }

  Popover.prototype.setContent = function () {
    var $tip    = this.tip()
    var title   = this.getTitle()
    var content = this.getContent()

    if (this.options.html) {
      var typeContent = typeof content

      if (this.options.sanitize) {
        title = this.sanitizeHtml(title)

        if (typeContent === 'string') {
          content = this.sanitizeHtml(content)
        }
      }

      $tip.find('.popover-title').html(title)
      $tip.find('.popover-content').children().detach().end()[
          typeContent === 'string' ? 'html' : 'append'
          ](content)
    } else {
      $tip.find('.popover-title').text(title)
      $tip.find('.popover-content').children().detach().end().text(content)
    }

    $tip.removeClass('fade top bottom left right in')

    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
    // this manually by checking the contents.
    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
  }

  Popover.prototype.hasContent = function () {
    return this.getTitle() || this.getContent()
  }

  Popover.prototype.getContent = function () {
    var $e = this.$element
    var o  = this.options

    return $e.attr('data-content')
        || (typeof o.content == 'function' ?
            o.content.call($e[0]) :
            o.content)
  }

  Popover.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
  }


  // POPOVER PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.popover')
      var options = typeof option == 'object' && option

      if (!data && /destroy|hide/.test(option)) return
      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.popover

  $.fn.popover             = Plugin
  $.fn.popover.Constructor = Popover


  // POPOVER NO CONFLICT
  // ===================

  $.fn.popover.noConflict = function () {
    $.fn.popover = old
    return this
  }

}(jQuery);

/*!
 * 360 degree Image Slider v2.0.4
 * http://gaurav.jassal.me
 *
 * Copyright 2015, gaurav@jassal.me
 * Dual licensed under the MIT or GPL Version 3 licenses.
 *
 */
(function($) {
	'use strict';
	/**
	 * @class ThreeSixty
	 * **The ThreeSixty slider class**.
	 *
	 * This as jQuery plugin to create 360 degree product image slider.
	 * The plugin is full customizable with number of options provided. The plugin
	 * have the power to display images in any angle 360 degrees. This feature can be
	 * used successfully in many use cases e.g. on an e-commerce site to help customers
	 * look products in detail, from any angle they desire.
	 *
	 * **Features**
	 *
	 * - Smooth Animation
	 * - Plenty of option parameters for customization
	 * - Api interaction
	 * - Simple mouse interaction
	 * - Custom behavior tweaking
	 * - Support for touch devices
	 * - Easy to integrate
	 * - No flash
	 *
	 * Example code:
	 *      var product1 = $('.product1').ThreeSixty({
	 *        totalFrames: 72,
	 *        endFrame: 72,
	 *        currentFrame: 1,
	 *        imgList: '.threesixty_images',
	 *        progress: '.spinner',
	 *        imagePath:'/assets/product1/',
	 *        filePrefix: 'ipod-',
	 *        ext: '.jpg',
	 *        height: 265,
	 *        width: 400,
	 *        navigation: true
	 *      });
	 * **Note:** There are loads other options that you can override to customize
	 * this plugin.

	 * @extends jQuery
	 * @singleton
	 * @param {String} [el] jQuery selector string for the parent container
	 * @param {Object} [options] An optional config object
	 *
	 * @return this
	 */
	$.ThreeSixty = function(el, options) {
		// To avoid scope issues, use 'base' instead of 'this'
		// to reference this class from internal events and functions.
		var base = this,
		    AppConfig, frames = [],
		    VERSION = '2.0.5';
		// Access to jQuery and DOM versions of element
		/**
		 * @property {$el}
		 * jQuery Dom node attached to the slider inherits all jQuery public functions.
		 */
		base.$el = $(el);
		base.el = el;
		// Add a reverse reference to the DOM object
		base.$el.data('ThreeSixty', base);
		/**
		 * @method init
		 * The function extends the user options with default settings for the
		 * slider and initilize the slider.
		 * **Style Override example**
		 *
		 *      var product1 = $('.product1').ThreeSixty({
		 *        totalFrames: 72,
		 *        endFrame: 72,
		 *        currentFrame: 1,
		 *        imgList: '.threesixty_images',
		 *        progress: '.spinner',
		 *        imagePath:'/assets/product1/',
		 *        filePrefix: 'ipod-',
		 *        ext: '.jpg',
		 *        height: 265,
		 *        width: 400,
		 *        navigation: true,
		 *        styles: {
		 *          border: 2px solide #b4b4b4,
		 *          background: url(http://example.com/images/loader.gif) no-repeat
		 *        }
		 *      });
		 */
		base.init = function() {
			AppConfig = $.extend({}, $.ThreeSixty.defaultOptions, options);
			if(AppConfig.disableSpin) {
				AppConfig.currentFrame = 1;
				AppConfig.endFrame = 1;
			}
			base.initProgress();
			base.loadImages();
		};

		/*
		 * Function to resize the height of responsive slider.
		 */
		base.resize = function() {
			// calculate height
		};
		/**
		 * @method initProgress
		 * The function sets initial styles and start the progress indicator
		 * to show loading of images.
		 *
		 * @private
		 */
		base.initProgress = function() {
			base.$el.css({
				width: AppConfig.width + 'px',
				height: AppConfig.height + 'px',
				'background-image': 'none !important'
			});
			if(AppConfig.styles) {
				base.$el.css(AppConfig.styles);
			}

			base.responsive();

			base.$el.find(AppConfig.progress).css({
				marginTop: ((AppConfig.height / 2) - 15) + 'px'
			});
			base.$el.find(AppConfig.progress).fadeIn('slow');
			base.$el.find(AppConfig.imgList).hide();
		};

		/**
		 * @method loadImages
		 * @private
		 * The function asynchronously loads images and inject into the slider.
		 */
		base.loadImages = function() {
			var li, imageName, image, host, baseIndex;
			li = document.createElement('li');
			baseIndex = AppConfig.zeroBased ? 0 : 1;
			imageName = !AppConfig.imgArray ?
				AppConfig.domain + AppConfig.imagePath + AppConfig.filePrefix + base.zeroPad((AppConfig.loadedImages + baseIndex)) + AppConfig.ext + ((base.browser.isIE()) ? '?' + new Date().getTime() : '') :
				AppConfig.imgArray[AppConfig.loadedImages];
			image = $('<img>').attr('src', imageName).addClass('previous-image').appendTo(li);

			frames.push(image);

			base.$el.find(AppConfig.imgList).append(li);

			$(image).on( 'load', function () {
				base.imageLoaded();
			});
		};

		/**
		 * @method loadImages
		 * @private
		 * The function gets triggers once the image is loaded. We also update
		 * the progress percentage in this function.
		 */
		base.imageLoaded = function () {
			AppConfig.loadedImages += 1;
			$(AppConfig.progress + ' span').text(Math.floor(AppConfig.loadedImages / AppConfig.totalFrames * 100) + '%');
			if (AppConfig.loadedImages >= AppConfig.totalFrames) {
				if(AppConfig.disableSpin) {
					frames[0].removeClass('previous-image').addClass('current-image');
				}
				$(AppConfig.progress).fadeOut('slow', function () {
					$(this).hide();
					base.showImages();
					base.showNavigation();
				});
			} else {
				base.loadImages();
			}
		};

		/**
		 * @method loadImages
		 * @private
		 * This function is called when all the images are loaded.
		 * **The function does following operations**
		 * - Removes background image placeholder
		 * - Displays the 360 images
		 * - Initilizes mouse intraction events
		 */
		base.showImages = function () {
			base.$el.find('.txtC').fadeIn();
			base.$el.find(AppConfig.imgList).fadeIn();
			base.ready = true;
			AppConfig.ready = true;

			if (AppConfig.drag) {
				base.initEvents();
			}
			base.refresh();
			base.initPlugins();
			AppConfig.onReady();

			setTimeout(function() { base.responsive(); }, 50);
		};

		/**
		 * The function to initilize external plugin
		 */
		base.initPlugins = function () {
			$.each(AppConfig.plugins, function(i, plugin) {
				if(typeof $[plugin] === 'function') {
					$[plugin].call(base, base.$el, AppConfig);
				} else {
					throw new Error(plugin + ' not available.');
				}
			});
		};

		/**
		 * @method showNavigation
		 * Creates a navigation panel if navigation is set to true in the
		 * settings.
		 */
		base.showNavigation = function() {
			if (AppConfig.navigation && !AppConfig.navigation_init) {
				var nav_bar, next, previous, play_stop;

				nav_bar = $('<div/>').attr('class', 'nav_bar');

				next = $('<a/>').attr({
					'href': '#',
					'class': 'nav_bar_next'
				}).html('next');

				previous = $('<a/>').attr({
					'href': '#',
					'class': 'nav_bar_previous'
				}).html('previous');

				play_stop = $('<a/>').attr({
					'href': '#',
					'class': 'nav_bar_play'
				}).html('play');

				nav_bar.append(previous);
				nav_bar.append(play_stop);
				nav_bar.append(next);

				base.$el.prepend(nav_bar);

				next.bind('click touchstart', base.next);
				previous.bind('click touchstart', base.previous);
				play_stop.bind('click touchstart', base.play_stop);
				AppConfig.navigation_init = true;
			}
		};

		/**
		 * @method play_stop
		 * @private
		 * Function toggles the autoplay rotation of 360 slider
		 * @param {Object} [event] jQuery events object.
		 *
		 */

		base.play_stop = function(event) {
			event.preventDefault();

			if (!AppConfig.autoplay) {
				AppConfig.autoplay = true;
				AppConfig.play = setInterval(base.moveToNextFrame, AppConfig.playSpeed);
				$(event.currentTarget).removeClass('nav_bar_play').addClass('nav_bar_stop');
			} else {
				AppConfig.autoplay = false;
				$(event.currentTarget).removeClass('nav_bar_stop').addClass('nav_bar_play');
				clearInterval(AppConfig.play);
				AppConfig.play = null;
			}
		};

		/**
		 * @method next
		 * Using this function you can rotate 360 to next 5 frames.
		 * @param {Object} [event] jQuery events object.
		 *
		 */

		base.next = function(event) {
			if (event) { event.preventDefault(); }
			AppConfig.endFrame -= parseInt( AppConfig.prevNextFrames );
			base.refresh();
		};

		/**
		 * @method previous
		 * Using this function you can rotate 360 to previous 5 frames.
		 * @param {Object} [event] jQuery events object.
		 *
		 */
		base.previous = function(event) {
			if (event) { event.preventDefault(); }
			AppConfig.endFrame += parseInt( AppConfig.prevNextFrames );
			base.refresh();
		};

		/**
		 * @method play
		 * You are start the auto rotaion for the slider with this function.
		 *
		 */
		base.play = function(speed, direction) {
			var _speed = speed || AppConfig.playSpeed;
			var _direction = direction || AppConfig.autoplayDirection;
			AppConfig.autoplayDirection = _direction

			if (!AppConfig.autoplay) {
				AppConfig.autoplay = true;
				AppConfig.play = setInterval(base.moveToNextFrame, _speed);
			}
		};

		/**
		 * @method stop
		 * You can stop the auto rotation of the 360 slider with this function.
		 *
		 */

		base.stop = function() {
			if (AppConfig.autoplay) {
				AppConfig.autoplay = false;
				clearInterval(AppConfig.play);
				AppConfig.play = null;
			}
		};

		/**
		 * @method endFrame
		 * @private
		 * Function animates to previous frame
		 *
		 */
		base.moveToNextFrame = function () {
			if (AppConfig.autoplayDirection === 1) {
				AppConfig.endFrame -= 1;
			} else {
				AppConfig.endFrame += 1;
			}
			base.refresh();
		};

		/**
		 * @method gotoAndPlay
		 * @public
		 * Function animates to previous frame
		 *
		 */
		base.gotoAndPlay = function (n) {
			if( AppConfig.disableWrap ) {
				AppConfig.endFrame = n;
				base.refresh();
			} else {
				// Since we could be looped around grab the multiplier
				var multiplier = Math.ceil(AppConfig.endFrame / AppConfig.totalFrames);
				if(multiplier === 0) {
					multiplier = 1;
				}

				// Figure out the quickest path to the requested frame
				var realEndFrame = (multiplier > 1) ?
					AppConfig.endFrame - ((multiplier - 1) * AppConfig.totalFrames) :
					AppConfig.endFrame;

				var currentFromEnd = AppConfig.totalFrames - realEndFrame;

				// Jump past end if it's faster
				var newEndFrame = 0;
				if(n - realEndFrame > 0) {
					// Faster to move the difference ahead?
					if(n - realEndFrame < realEndFrame + (AppConfig.totalFrames - n)) {
						newEndFrame = AppConfig.endFrame + (n - realEndFrame);
					} else {
						newEndFrame = AppConfig.endFrame - (realEndFrame + (AppConfig.totalFrames - n));
					}
				} else {
					// Faster to move the distance back?
					if(realEndFrame - n < currentFromEnd + n) {
						newEndFrame = AppConfig.endFrame - (realEndFrame - n);
					} else {
						newEndFrame = AppConfig.endFrame + (currentFromEnd + n);
					}
				}

				// Now set the end frame
				if(realEndFrame !== n) {
					AppConfig.endFrame = newEndFrame;
					base.refresh();
				}
			}
		};


		/**
		 * @method initEvents
		 * @private
		 * Function initilizes all the mouse and touch events for 360 slider movement.
		 *
		 */
		base.initEvents = function () {
			base.$el.bind('mousedown touchstart touchmove touchend mousemove click', function (event) {

				event.preventDefault();

				if ((event.type === 'mousedown' && event.which === 1) || event.type === 'touchstart') {
					AppConfig.pointerStartPosX = base.getPointerEvent(event).pageX;
					AppConfig.dragging = true;
					AppConfig.onDragStart(AppConfig.currentFrame);
				} else if (event.type === 'touchmove') {
					base.trackPointer(event);
				} else if (event.type === 'touchend') {
					AppConfig.dragging = false;
					AppConfig.onDragStop(AppConfig.endFrame);
				}
			});

			$(document).bind('mouseup', function (event) {
				AppConfig.dragging = false;
				AppConfig.onDragStop(AppConfig.endFrame);
				$(this).css('cursor', 'none');
			});

			$(window).bind('resize', function (event) {
				base.responsive();
			});

			$(document).bind('mousemove', function (event) {
				if (AppConfig.dragging) {
					event.preventDefault();
					if(!base.browser.isIE && AppConfig.showCursor) {
						base.$el.css('cursor', 'url(assets/images/hand_closed.png), auto');
					}
				} else {
					if(!base.browser.isIE && AppConfig.showCursor) {
						base.$el.css('cursor', 'url(assets/images/hand_open.png), auto');
					}
				}
				base.trackPointer(event);

			});

			$(window).resize(function() {
				base.resize();
			});
		};

		/**
		 * @method getPointerEvent
		 * @private
		 * Function returns touch pointer events
		 *
		 * @params {Object} [event]
		 */
		base.getPointerEvent = function (event) {
			return event.originalEvent.targetTouches ? event.originalEvent.targetTouches[0] : event;
		};

		/**
		 * @method trackPointer
		 * @private
		 * Function calculates the distance between the start pointer and end pointer/
		 *
		 * @params {Object} [event]
		 */
		base.trackPointer = function (event) {
			if (AppConfig.ready && AppConfig.dragging) {
				AppConfig.pointerEndPosX = base.getPointerEvent(event).pageX;
				if (AppConfig.monitorStartTime < new Date().getTime() - AppConfig.monitorInt) {
					AppConfig.pointerDistance = AppConfig.pointerEndPosX - AppConfig.pointerStartPosX;
					if(AppConfig.pointerDistance > 0){
						AppConfig.endFrame = AppConfig.currentFrame + Math.ceil((AppConfig.totalFrames - 1) * AppConfig.speedMultiplier * (AppConfig.pointerDistance / base.$el.width()));
					}else{
						AppConfig.endFrame = AppConfig.currentFrame + Math.floor((AppConfig.totalFrames - 1) * AppConfig.speedMultiplier * (AppConfig.pointerDistance / base.$el.width()));
					}

					if( AppConfig.disableWrap ) {
						AppConfig.endFrame = Math.min(AppConfig.totalFrames - (AppConfig.zeroBased ? 1 : 0), AppConfig.endFrame);
						AppConfig.endFrame = Math.max((AppConfig.zeroBased ? 0 : 1), AppConfig.endFrame);
					}
					base.refresh();
					AppConfig.monitorStartTime = new Date().getTime();
					AppConfig.pointerStartPosX = base.getPointerEvent(event).pageX;
				}
			}
		};

		/**
		 * @method refresh
		 * @public
		 * Function refeshes the timer and set interval for render cycle.
		 *
		 */

		base.refresh = function () {
			if (AppConfig.ticker === 0) {
				AppConfig.ticker = setInterval(base.render, Math.round(1000 / AppConfig.framerate));
			}
		};

		/**
		 * @method refresh
		 * @private
		 * Function render the animation frames on the screen with easing effect.
		 */

		base.render = function () {
			var frameEasing;
			if (AppConfig.currentFrame !== AppConfig.endFrame) {
				frameEasing = AppConfig.endFrame < AppConfig.currentFrame ? Math.floor((AppConfig.endFrame - AppConfig.currentFrame) * 0.1) : Math.ceil((AppConfig.endFrame - AppConfig.currentFrame) * 0.1);
				base.hidePreviousFrame();
				AppConfig.currentFrame += frameEasing;
				base.showCurrentFrame();
				base.$el.trigger('frameIndexChanged', [base.getNormalizedCurrentFrame(), AppConfig.totalFrames]);
			} else {
				window.clearInterval(AppConfig.ticker);
				AppConfig.ticker = 0;
			}
		};

		/**
		 * @method hidePreviousFrame
		 * @private
		 * Function hide the previous frame in the animation loop.
		 */

		base.hidePreviousFrame = function () {
			frames[base.getNormalizedCurrentFrame()].removeClass('current-image').addClass('previous-image');
		};

		/**
		 * @method showCurrentFrame
		 * @private
		 * Function shows the current frame in the animation loop.
		 */
		base.showCurrentFrame = function () {
			frames[base.getNormalizedCurrentFrame()].removeClass('previous-image').addClass('current-image');
		};

		/**
		 * @method getNormalizedCurrentFrame
		 * @private
		 * Function normalize and calculate the current frame once the user release the mouse and release touch event.
		 */

		base.getNormalizedCurrentFrame = function () {
			var c, e;

			if ( !AppConfig.disableWrap ) {
				c = Math.ceil(AppConfig.currentFrame % AppConfig.totalFrames);
				if (c < 0) {
					c += AppConfig.totalFrames - (AppConfig.zeroBased ? 1 : 0);
				}
			} else {
				c = Math.min(AppConfig.currentFrame, AppConfig.totalFrames - (AppConfig.zeroBased ? 1 : 0));
				e = Math.min(AppConfig.endFrame, AppConfig.totalFrames - (AppConfig.zeroBased ? 1 : 0));
				c = Math.max(c, (AppConfig.zeroBased ? 0 : 1));
				e = Math.max(e, (AppConfig.zeroBased ? 0 : 1));
				AppConfig.currentFrame = c;
				AppConfig.endFrame = e;
			}

			return c;
		};

		/*
		 * @method getCurrentFrame
		 * Function returns the current active frame.
		 *
		 * @return Number
		 */

		base.getCurrentFrame = function() {
			return AppConfig.currentFrame;
		};

		/*
		 * @method responsive
		 * Function calculates and set responsive height and width
		 *
		 */

		base.responsive = function() {
			if(AppConfig.responsive) {
				base.$el.css({
					height: base.$el.find('.current-image').first().css('height'),
					width: '100%'
				});
			}
		};

		/**
		 * Function to return with zero padding.
		 */
		base.zeroPad = function (num) {
			function pad(number, length) {
				var str = number.toString();
				if(AppConfig.zeroPadding) {
					while (str.length < length) {
						str = '0' + str;
					}
				}
				return str;
			}

			var approximateLog = Math.log(AppConfig.totalFrames) / Math.LN10;
			var roundTo = 1e3;
			var roundedLog = Math.round(approximateLog * roundTo) / roundTo;
			var numChars = Math.floor(roundedLog) + 1;
			return pad(num, numChars);
		};

		base.browser = {};

		/**
		 * Function to detect if the brower is IE
		 * @return {boolean}
		 *
		 * http://msdn.microsoft.com/en-gb/library/ms537509(v=vs.85).aspx
		 */
		base.browser.isIE = function () {
			var rv = -1;
			if (navigator.appName === 'Microsoft Internet Explorer')
			{
				var ua = navigator.userAgent;
				var re  = new RegExp('MSIE ([0-9]{1,}[\\.0-9]{0,})');
				if (re.exec(ua) !== null){
					rv = parseFloat( RegExp.$1 );
				}
			}

			return rv !== -1;
		};


		/**
		 * @method getConfig
		 * The function returns the extended version of config object the plugin is going to
		 * user.
		 *
		 * @public
		 *
		 * @return Object
		 */
		base.getConfig = function() {
			return AppConfig;
		};

		$.ThreeSixty.defaultOptions = {
			/**
			 * @cfg {Boolean} dragging [dragging=false]
			 * @private
			 * Private propery contains a flags if users is in dragging mode.
			 */
			dragging: false,
			/**
			 * @cfg {Boolean} ready [ready=false]
			 * @private
			 * Private propery is set to true is all assets are loading and application is
			 * ready to render 360 slider.
			 */
			ready: false,
			/**
			 * @cfg {Number} pointerStartPosX
			 * @private
			 * private property mouse pointer start x position when user starts dragging slider.
			 */
			pointerStartPosX: 0,
			/**
			 * @cfg {Number} pointerEndPosX
			 * @private
			 * private property mouse pointer start x position when user end dragging slider.
			 */
			pointerEndPosX: 0,
			/**
			 * @cfg {Number} pointerDistance
			 * @private
			 * private property contains the distance between the pointerStartPosX and pointerEndPosX
			 */
			pointerDistance: 0,
			/**
			 * @cfg {Number} monitorStartTime
			 * @private
			 * private property contains time user took in dragging mouse from pointerStartPosX and pointerEndPosX
			 */
			monitorStartTime: 0,
			monitorInt: 10,
			/**
			 * @cfg {Number} ticker
			 * @private
			 * Timer event that renders the 360
			 */
			ticker: 0,
			/**
			 * @cfg {Number} speedMultiplier
			 * This property controls the sensitivity for the 360 slider
			 */
			speedMultiplier: 7,
			/**
			 * @cfg {Number} totalFrames
			 * Set total number for frames used in the 360 rotation
			 */
			totalFrames: 180,
			/**
			 * @cfg {Number} currentFrame
			 * Current frame of the slider.
			 */
			currentFrame: 0,
			/**
			 * @cfg {Array} endFrame
			 * Private perperty contains information about the end frame when user slides the slider.
			 */
			endFrame: 0,
			/**
			 * @cfg {Number} loadedImages
			 * Private property contains count of loaded images.
			 */
			loadedImages: 0,
			/**
			 * @cfg {Array} framerate
			 * Set framerate for the slider animation
			 */
			framerate: 60,
			/**
			 * @cfg {String} domains
			 * Set comma seprated list of all parallel domain from where 360 assets needs to be loaded.
			 */
			domains: null,
			/**
			 * @cfg {String} domain
			 * Domain from where assets needs to be loaded. Use this propery is you want to load all assets from
			 * single domain.
			 */
			domain: '',
			/**
			 * @cfg {Boolean} parallel
			 * Set to true if you want to load assets from parallel domain. Default false
			 */
			parallel: false,
			/**
			 * @cfg {Number} queueAmount
			 * Set number of calls to be made on parallel domains.
			 */
			queueAmount: 8,
			/**
			 * @cfg {Number} idle
			 * Mouse Inactivite idle time in seconds. If set more than 0 will auto spine the slider
			 */
			idle: 0,
			/**
			 * @cfg {String} filePrefix
			 * Prefix for the image file name before the numeric value.
			 */
			filePrefix: '',
			/**
			 * @cfg {String} ext [ext=.png]
			 * Slider image extension.
			 */
			ext: 'png',
			/**
			 * @cfg {Object} height [300]
			 * Height of the slider
			 */
			height: 300,
			/**
			 * @cfg {Number} width [300]
			 * Width of the slider
			 */
			width: 300,
			/**
			 * @cfg {Object} styles
			 * CSS Styles for the 360 slider
			 */
			styles: {},
			/**
			 * @cfg {Boolean} navigation[false]
			 * State if navigation controls are visible or not.
			 */
			navigation: false,
			/**
			 * @cfg {Boolean} autoplay[false]
			 * Autoplay the 360 animation
			 */
			autoplay: false,
			/**
			 * @cfg {number} autoplayDirection [1]
			 * Direction for autoplay the 360 animation. 1 for right spin, and -1 for left spin.
			 */
			autoplayDirection: 1,
			/**
			 * Property to disable auto spin
			 * @type {Boolean}
			 */
			disableSpin: false,
			/**
			 * Property to disable infinite wrap
			 * @type {Boolean}
			 */
			disableWrap: false,
			/**
			 * Responsive width
			 * @type {Boolean}
			 */
			responsive: false,
			/**
			 * Zero Padding for filenames
			 * @type {Boolean}
			 */
			zeroPadding: false,
			/**
			 * Zero based for image filenames starting at 0
			 * @type {Boolean}
			 */
			zeroBased: false,
			/**
			 * @type {Array}
			 * List of plugins
			 */
			plugins: [],
			/**
			 * @type {Boolean}
			 * Show hand cursor on drag
			 */
			showCursor: false,
			/**
			 * @cfg {Boolean} drag
			 * Set it to false if you want to disable mousedrag or touch events
			 */
			drag: true,
			/**
			 * @cfg {Function} onReady
			 * Callback triggers once all images are loaded and ready to render on the screen
			 */
			onReady: function() {},
			/**
			 * @cfg {Function} onDragStart
			 * Callback triggers when a user initiates dragging
			 */
			onDragStart: function() {},
			/**
			 * @cfg {Function} onDragStop
			 * Callback triggers when a user releases after dragging
			 */
			onDragStop: function() {},
			/**
			 * @cfg {String} imgList
			 * Set ul element where image will be loaded
			 */
			imgList: '.threesixty_images',
			/**
			 * @cfg {Array} imgArray
			 * Use set of images in array to load images
			 */
			imgArray: null,
			/**
			 * @cfg {Number} playSpeed
			 * Value to control the speed of play button rotation
			 */
			playSpeed: 100,

			prevNextFrames: 5,
		};
		base.init();
	};

	$.fn.ThreeSixty = function(options) {
		return Object.create(new $.ThreeSixty(this, options));
	};
}(jQuery));
/**
 *
 * Object.create method for perform as a fallback if method not available.
 * The syntax just takes away the illusion that JavaScript uses Classical Inheritance.
 */
if(typeof Object.create !== 'function') {
	Object.create = function(o) {
		'use strict';

		function F() {}
		F.prototype = o;
		return new F();
	};
}
/**
 * Swiper 12.1.3
 * Most modern mobile touch slider and framework with hardware accelerated transitions
 * https://swiperjs.com
 *
 * Copyright 2014-2026 Vladimir Kharlampidi
 *
 * Released under the MIT License
 *
 * Released on: March 24, 2026
 */

var wdSwiper = (function () {
	'use strict';

	/**
	 * SSR Window 5.0.1
	 * Better handling for window object in SSR environment
	 * https://github.com/nolimits4web/ssr-window
	 *
	 * Copyright 2025, Vladimir Kharlampidi
	 *
	 * Licensed under MIT
	 *
	 * Released on: June 27, 2025
	 */
	/* eslint-disable no-param-reassign */
	function isObject$1(obj) {
		return obj !== null && typeof obj === 'object' && 'constructor' in obj && obj.constructor === Object;
	}
	function extend$1(target = {}, src = {}) {
		const noExtend = ['__proto__', 'constructor', 'prototype'];
		Object.keys(src).filter(key => noExtend.indexOf(key) < 0).forEach(key => {
			if (typeof target[key] === 'undefined') target[key] = src[key];else if (isObject$1(src[key]) && isObject$1(target[key]) && Object.keys(src[key]).length > 0) {
				extend$1(target[key], src[key]);
			}
		});
	}
	const ssrDocument = {
		body: {},
		addEventListener() {},
		removeEventListener() {},
		activeElement: {
			blur() {},
			nodeName: ''
		},
		querySelector() {
			return null;
		},
		querySelectorAll() {
			return [];
		},
		getElementById() {
			return null;
		},
		createEvent() {
			return {
				initEvent() {}
			};
		},
		createElement() {
			return {
				children: [],
				childNodes: [],
				style: {},
				setAttribute() {},
				getElementsByTagName() {
					return [];
				}
			};
		},
		createElementNS() {
			return {};
		},
		importNode() {
			return null;
		},
		location: {
			hash: '',
			host: '',
			hostname: '',
			href: '',
			origin: '',
			pathname: '',
			protocol: '',
			search: ''
		}
	};
	function getDocument() {
		const doc = typeof document !== 'undefined' ? document : {};
		extend$1(doc, ssrDocument);
		return doc;
	}
	const ssrWindow = {
		document: ssrDocument,
		navigator: {
			userAgent: ''
		},
		location: {
			hash: '',
			host: '',
			hostname: '',
			href: '',
			origin: '',
			pathname: '',
			protocol: '',
			search: ''
		},
		history: {
			replaceState() {},
			pushState() {},
			go() {},
			back() {}
		},
		CustomEvent: function CustomEvent() {
			return this;
		},
		addEventListener() {},
		removeEventListener() {},
		getComputedStyle() {
			return {
				getPropertyValue() {
					return '';
				}
			};
		},
		Image() {},
		Date() {},
		screen: {},
		setTimeout() {},
		clearTimeout() {},
		matchMedia() {
			return {};
		},
		requestAnimationFrame(callback) {
			if (typeof setTimeout === 'undefined') {
				callback();
				return null;
			}
			return setTimeout(callback, 0);
		},
		cancelAnimationFrame(id) {
			if (typeof setTimeout === 'undefined') {
				return;
			}
			clearTimeout(id);
		}
	};
	function getWindow() {
		const win = typeof window !== 'undefined' ? window : {};
		extend$1(win, ssrWindow);
		return win;
	}

	function classesToTokens(classes = '') {
		return classes.trim().split(' ').filter(c => !!c.trim());
	}

	function deleteProps(obj) {
		const object = obj;
		Object.keys(object).forEach(key => {
			try {
				object[key] = null;
			} catch (e) {
				// no getter for object
			}
			try {
				delete object[key];
			} catch (e) {
				// something got wrong
			}
		});
	}
	function nextTick(callback, delay = 0) {
		return setTimeout(callback, delay);
	}
	function now() {
		return Date.now();
	}
	function getComputedStyle$1(el) {
		const window = getWindow();
		let style;
		if (window.getComputedStyle) {
			style = window.getComputedStyle(el, null);
		}
		if (!style && el.currentStyle) {
			style = el.currentStyle;
		}
		if (!style) {
			style = el.style;
		}
		return style;
	}
	function getTranslate(el, axis = 'x') {
		const window = getWindow();
		let matrix;
		let curTransform;
		let transformMatrix;
		const curStyle = getComputedStyle$1(el);
		if (window.WebKitCSSMatrix) {
			curTransform = curStyle.transform || curStyle.webkitTransform;
			if (curTransform.split(',').length > 6) {
				curTransform = curTransform.split(', ').map(a => a.replace(',', '.')).join(', ');
			}
			// Some old versions of Webkit choke when 'none' is passed; pass
			// empty string instead in this case
			transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
		} else {
			transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
			matrix = transformMatrix.toString().split(',');
		}
		if (axis === 'x') {
			// Latest Chrome and webkits Fix
			if (window.WebKitCSSMatrix) curTransform = transformMatrix.m41;
			// Crazy IE10 Matrix
			else if (matrix.length === 16) curTransform = parseFloat(matrix[12]);
			// Normal Browsers
			else curTransform = parseFloat(matrix[4]);
		}
		if (axis === 'y') {
			// Latest Chrome and webkits Fix
			if (window.WebKitCSSMatrix) curTransform = transformMatrix.m42;
			// Crazy IE10 Matrix
			else if (matrix.length === 16) curTransform = parseFloat(matrix[13]);
			// Normal Browsers
			else curTransform = parseFloat(matrix[5]);
		}
		return curTransform || 0;
	}
	function isObject(o) {
		return typeof o === 'object' && o !== null && o.constructor && Object.prototype.toString.call(o).slice(8, -1) === 'Object';
	}
	function isNode(node) {
		// eslint-disable-next-line
		if (typeof window !== 'undefined' && typeof window.HTMLElement !== 'undefined') {
			return node instanceof HTMLElement;
		}
		return node && (node.nodeType === 1 || node.nodeType === 11);
	}
	function extend(...args) {
		const to = Object(args[0]);
		for (let i = 1; i < args.length; i += 1) {
			const nextSource = args[i];
			if (nextSource !== undefined && nextSource !== null && !isNode(nextSource)) {
				const keysArray = Object.keys(Object(nextSource)).filter(key => key !== '__proto__' && key !== 'constructor' && key !== 'prototype');
				for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex += 1) {
					const nextKey = keysArray[nextIndex];
					const desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
					if (desc !== undefined && desc.enumerable) {
						if (isObject(to[nextKey]) && isObject(nextSource[nextKey])) {
							if (nextSource[nextKey].__swiper__) {
								to[nextKey] = nextSource[nextKey];
							} else {
								extend(to[nextKey], nextSource[nextKey]);
							}
						} else if (!isObject(to[nextKey]) && isObject(nextSource[nextKey])) {
							to[nextKey] = {};
							if (nextSource[nextKey].__swiper__) {
								to[nextKey] = nextSource[nextKey];
							} else {
								extend(to[nextKey], nextSource[nextKey]);
							}
						} else {
							to[nextKey] = nextSource[nextKey];
						}
					}
				}
			}
		}
		return to;
	}
	function setCSSProperty(el, varName, varValue) {
		el.style.setProperty(varName, varValue);
	}
	function animateCSSModeScroll({
									  swiper,
									  targetPosition,
									  side
								  }) {
		const window = getWindow();
		const startPosition = -swiper.translate;
		let startTime = null;
		let time;
		const duration = swiper.params.speed;
		swiper.wrapperEl.style.scrollSnapType = 'none';
		window.cancelAnimationFrame(swiper.cssModeFrameID);
		const dir = targetPosition > startPosition ? 'next' : 'prev';
		const isOutOfBound = (current, target) => {
			return dir === 'next' && current >= target || dir === 'prev' && current <= target;
		};
		const animate = () => {
			time = new Date().getTime();
			if (startTime === null) {
				startTime = time;
			}
			const progress = Math.max(Math.min((time - startTime) / duration, 1), 0);
			const easeProgress = 0.5 - Math.cos(progress * Math.PI) / 2;
			let currentPosition = startPosition + easeProgress * (targetPosition - startPosition);
			if (isOutOfBound(currentPosition, targetPosition)) {
				currentPosition = targetPosition;
			}
			swiper.wrapperEl.scrollTo({
				[side]: currentPosition
			});
			if (isOutOfBound(currentPosition, targetPosition)) {
				swiper.wrapperEl.style.overflow = 'hidden';
				swiper.wrapperEl.style.scrollSnapType = '';
				setTimeout(() => {
					swiper.wrapperEl.style.overflow = '';
					swiper.wrapperEl.scrollTo({
						[side]: currentPosition
					});
				});
				window.cancelAnimationFrame(swiper.cssModeFrameID);
				return;
			}
			swiper.cssModeFrameID = window.requestAnimationFrame(animate);
		};
		animate();
	}
	function getSlideTransformEl(slideEl) {
		return slideEl.querySelector('.swiper-slide-transform') || slideEl.shadowRoot && slideEl.shadowRoot.querySelector('.swiper-slide-transform') || slideEl;
	}
	function elementChildren(element, selector = '') {
		const window = getWindow();
		const children = [...element.children];
		if (window.HTMLSlotElement && element instanceof HTMLSlotElement) {
			children.push(...element.assignedElements());
		}
		if (!selector) {
			return children;
		}
		return children.filter(el => el.matches(selector));
	}
	function elementIsChildOfSlot(el, slot) {
		// Breadth-first search through all parent's children and assigned elements
		const elementsQueue = [slot];
		while (elementsQueue.length > 0) {
			const elementToCheck = elementsQueue.shift();
			if (el === elementToCheck) {
				return true;
			}
			elementsQueue.push(...elementToCheck.children, ...(elementToCheck.shadowRoot ? elementToCheck.shadowRoot.children : []), ...(elementToCheck.assignedElements ? elementToCheck.assignedElements() : []));
		}
	}
	function elementIsChildOf(el, parent) {
		const window = getWindow();
		let isChild = parent.contains(el);
		if (!isChild && window.HTMLSlotElement && parent instanceof HTMLSlotElement) {
			const children = [...parent.assignedElements()];
			isChild = children.includes(el);
			if (!isChild) {
				isChild = elementIsChildOfSlot(el, parent);
			}
		}
		return isChild;
	}
	function showWarning(text) {
		try {
			console.warn(text);
			return;
		} catch (err) {
			// err
		}
	}
	function createElement(tag, classes = []) {
		const el = document.createElement(tag);
		el.classList.add(...(Array.isArray(classes) ? classes : classesToTokens(classes)));
		return el;
	}
	function elementOffset(el) {
		const window = getWindow();
		const document = getDocument();
		const box = el.getBoundingClientRect();
		const body = document.body;
		const clientTop = el.clientTop || body.clientTop || 0;
		const clientLeft = el.clientLeft || body.clientLeft || 0;
		const scrollTop = el === window ? window.scrollY : el.scrollTop;
		const scrollLeft = el === window ? window.scrollX : el.scrollLeft;
		return {
			top: box.top + scrollTop - clientTop,
			left: box.left + scrollLeft - clientLeft
		};
	}
	function elementPrevAll(el, selector) {
		const prevEls = [];
		while (el.previousElementSibling) {
			const prev = el.previousElementSibling; // eslint-disable-line
			if (selector) {
				if (prev.matches(selector)) prevEls.push(prev);
			} else prevEls.push(prev);
			el = prev;
		}
		return prevEls;
	}
	function elementNextAll(el, selector) {
		const nextEls = [];
		while (el.nextElementSibling) {
			const next = el.nextElementSibling; // eslint-disable-line
			if (selector) {
				if (next.matches(selector)) nextEls.push(next);
			} else nextEls.push(next);
			el = next;
		}
		return nextEls;
	}
	function elementStyle(el, prop) {
		const window = getWindow();
		return window.getComputedStyle(el, null).getPropertyValue(prop);
	}
	function elementIndex(el) {
		let child = el;
		let i;
		if (child) {
			i = 0;
			// eslint-disable-next-line
			while ((child = child.previousSibling) !== null) {
				if (child.nodeType === 1) i += 1;
			}
			return i;
		}
		return undefined;
	}
	function elementParents(el, selector) {
		const parents = []; // eslint-disable-line
		let parent = el.parentElement; // eslint-disable-line
		while (parent) {
			if (selector) {
				if (parent.matches(selector)) parents.push(parent);
			} else {
				parents.push(parent);
			}
			parent = parent.parentElement;
		}
		return parents;
	}
	function elementTransitionEnd(el, callback) {
		function fireCallBack(e) {
			if (e.target !== el) return;
			callback.call(el, e);
			el.removeEventListener('transitionend', fireCallBack);
		}
		if (callback) {
			el.addEventListener('transitionend', fireCallBack);
		}
	}
	function elementOuterSize(el, size, includeMargins) {
		const window = getWindow();
		if (includeMargins) {
			return el[size === 'width' ? 'offsetWidth' : 'offsetHeight'] + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-right' : 'margin-top')) + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-left' : 'margin-bottom'));
		}
		return el.offsetWidth;
	}
	function makeElementsArray(el) {
		return (Array.isArray(el) ? el : [el]).filter(e => !!e);
	}
	function getRotateFix(swiper) {
		return v => {
			if (Math.abs(v) > 0 && swiper.browser && swiper.browser.need3dFix && Math.abs(v) % 90 === 0) {
				return v + 0.001;
			}
			return v;
		};
	}
	function setInnerHTML(el, html = '') {
		if (typeof trustedTypes !== 'undefined') {
			el.innerHTML = trustedTypes.createPolicy('html', {
				createHTML: s => s
			}).createHTML(html);
		} else {
			el.innerHTML = html;
		}
	}

	let support;
	function calcSupport() {
		const window = getWindow();
		const document = getDocument();
		return {
			smoothScroll: document.documentElement && document.documentElement.style && 'scrollBehavior' in document.documentElement.style,
			touch: !!('ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch)
		};
	}
	function getSupport() {
		if (!support) {
			support = calcSupport();
		}
		return support;
	}

	let deviceCached;
	function calcDevice({
							userAgent
						} = {}) {
		const support = getSupport();
		const window = getWindow();
		const platform = window.navigator.platform;
		const ua = userAgent || window.navigator.userAgent;
		const device = {
			ios: false,
			android: false
		};
		const screenWidth = window.screen.width;
		const screenHeight = window.screen.height;
		const android = ua.match(/(Android);?[\s\/]+([\d.]+)?/); // eslint-disable-line
		let ipad = ua.match(/(iPad)(?!\1).*OS\s([\d_]+)/);
		const ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
		const iphone = !ipad && ua.match(/(iPhone\sOS|iOS)\s([\d_]+)/);
		const windows = platform === 'Win32';
		let macos = platform === 'MacIntel';

		// iPadOs 13 fix
		const iPadScreens = ['1024x1366', '1366x1024', '834x1194', '1194x834', '834x1112', '1112x834', '768x1024', '1024x768', '820x1180', '1180x820', '810x1080', '1080x810'];
		if (!ipad && macos && support.touch && iPadScreens.indexOf(`${screenWidth}x${screenHeight}`) >= 0) {
			ipad = ua.match(/(Version)\/([\d.]+)/);
			if (!ipad) ipad = [0, 1, '13_0_0'];
			macos = false;
		}

		// Android
		if (android && !windows) {
			device.os = 'android';
			device.android = true;
		}
		if (ipad || iphone || ipod) {
			device.os = 'ios';
			device.ios = true;
		}

		// Export object
		return device;
	}
	function getDevice(overrides = {}) {
		if (!deviceCached) {
			deviceCached = calcDevice(overrides);
		}
		return deviceCached;
	}

	let browser;
	function calcBrowser() {
		const window = getWindow();
		const device = getDevice();
		let needPerspectiveFix = false;
		function isSafari() {
			const ua = window.navigator.userAgent.toLowerCase();
			return ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0;
		}
		if (isSafari()) {
			const ua = String(window.navigator.userAgent);
			if (ua.includes('Version/')) {
				const [major, minor] = ua.split('Version/')[1].split(' ')[0].split('.').map(num => Number(num));
				needPerspectiveFix = major < 16 || major === 16 && minor < 2;
			}
		}
		const isWebView = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(window.navigator.userAgent);
		const isSafariBrowser = isSafari();
		const need3dFix = isSafariBrowser || isWebView && device.ios;
		return {
			isSafari: needPerspectiveFix || isSafariBrowser,
			needPerspectiveFix,
			need3dFix,
			isWebView
		};
	}
	function getBrowser() {
		if (!browser) {
			browser = calcBrowser();
		}
		return browser;
	}

	function Resize({
						swiper,
						on,
						emit
					}) {
		const window = getWindow();
		let observer = null;
		let animationFrame = null;
		const resizeHandler = () => {
			if (!swiper || swiper.destroyed || !swiper.initialized) return;
			emit('beforeResize');
			emit('resize');
		};
		const createObserver = () => {
			if (!swiper || swiper.destroyed || !swiper.initialized) return;
			observer = new ResizeObserver(entries => {
				animationFrame = window.requestAnimationFrame(() => {
					const {
						width,
						height
					} = swiper;
					let newWidth = width;
					let newHeight = height;
					entries.forEach(({
										 contentBoxSize,
										 contentRect,
										 target
									 }) => {
						if (target && target !== swiper.el) return;
						newWidth = contentRect ? contentRect.width : (contentBoxSize[0] || contentBoxSize).inlineSize;
						newHeight = contentRect ? contentRect.height : (contentBoxSize[0] || contentBoxSize).blockSize;
					});
					if (newWidth !== width || newHeight !== height) {
						resizeHandler();
					}
				});
			});
			observer.observe(swiper.el);
		};
		const removeObserver = () => {
			if (animationFrame) {
				window.cancelAnimationFrame(animationFrame);
			}
			if (observer && observer.unobserve && swiper.el) {
				observer.unobserve(swiper.el);
				observer = null;
			}
		};
		const orientationChangeHandler = () => {
			if (!swiper || swiper.destroyed || !swiper.initialized) return;
			emit('orientationchange');
		};
		on('init', () => {
			if (swiper.params.resizeObserver && typeof window.ResizeObserver !== 'undefined') {
				createObserver();
				return;
			}
			window.addEventListener('resize', resizeHandler);
			window.addEventListener('orientationchange', orientationChangeHandler);
		});
		on('destroy', () => {
			removeObserver();
			window.removeEventListener('resize', resizeHandler);
			window.removeEventListener('orientationchange', orientationChangeHandler);
		});
	}

	function Observer({
						  swiper,
						  extendParams,
						  on,
						  emit
					  }) {
		const observers = [];
		const window = getWindow();
		const attach = (target, options = {}) => {
			const ObserverFunc = window.MutationObserver || window.WebkitMutationObserver;
			const observer = new ObserverFunc(mutations => {
				// The observerUpdate event should only be triggered
				// once despite the number of mutations.  Additional
				// triggers are redundant and are very costly
				if (swiper.__preventObserver__) return;
				if (mutations.length === 1) {
					emit('observerUpdate', mutations[0]);
					return;
				}
				const observerUpdate = function observerUpdate() {
					emit('observerUpdate', mutations[0]);
				};
				if (window.requestAnimationFrame) {
					window.requestAnimationFrame(observerUpdate);
				} else {
					window.setTimeout(observerUpdate, 0);
				}
			});
			observer.observe(target, {
				attributes: typeof options.attributes === 'undefined' ? true : options.attributes,
				childList: swiper.isElement || (typeof options.childList === 'undefined' ? true : options).childList,
				characterData: typeof options.characterData === 'undefined' ? true : options.characterData
			});
			observers.push(observer);
		};
		const init = () => {
			if (!swiper.params.observer) return;
			if (swiper.params.observeParents) {
				const containerParents = elementParents(swiper.hostEl);
				for (let i = 0; i < containerParents.length; i += 1) {
					attach(containerParents[i]);
				}
			}
			// Observe container
			attach(swiper.hostEl, {
				childList: swiper.params.observeSlideChildren
			});

			// Observe wrapper
			attach(swiper.wrapperEl, {
				attributes: false
			});
		};
		const destroy = () => {
			observers.forEach(observer => {
				observer.disconnect();
			});
			observers.splice(0, observers.length);
		};
		extendParams({
			observer: false,
			observeParents: false,
			observeSlideChildren: false
		});
		on('init', init);
		on('destroy', destroy);
	}

	/* eslint-disable no-underscore-dangle */

	var eventsEmitter = {
		on(events, handler, priority) {
			const self = this;
			if (!self.eventsListeners || self.destroyed) return self;
			if (typeof handler !== 'function') return self;
			const method = priority ? 'unshift' : 'push';
			events.split(' ').forEach(event => {
				if (!self.eventsListeners[event]) self.eventsListeners[event] = [];
				self.eventsListeners[event][method](handler);
			});
			return self;
		},
		once(events, handler, priority) {
			const self = this;
			if (!self.eventsListeners || self.destroyed) return self;
			if (typeof handler !== 'function') return self;
			function onceHandler(...args) {
				self.off(events, onceHandler);
				if (onceHandler.__emitterProxy) {
					delete onceHandler.__emitterProxy;
				}
				handler.apply(self, args);
			}
			onceHandler.__emitterProxy = handler;
			return self.on(events, onceHandler, priority);
		},
		onAny(handler, priority) {
			const self = this;
			if (!self.eventsListeners || self.destroyed) return self;
			if (typeof handler !== 'function') return self;
			const method = priority ? 'unshift' : 'push';
			if (self.eventsAnyListeners.indexOf(handler) < 0) {
				self.eventsAnyListeners[method](handler);
			}
			return self;
		},
		offAny(handler) {
			const self = this;
			if (!self.eventsListeners || self.destroyed) return self;
			if (!self.eventsAnyListeners) return self;
			const index = self.eventsAnyListeners.indexOf(handler);
			if (index >= 0) {
				self.eventsAnyListeners.splice(index, 1);
			}
			return self;
		},
		off(events, handler) {
			const self = this;
			if (!self.eventsListeners || self.destroyed) return self;
			if (!self.eventsListeners) return self;
			events.split(' ').forEach(event => {
				if (typeof handler === 'undefined') {
					self.eventsListeners[event] = [];
				} else if (self.eventsListeners[event]) {
					self.eventsListeners[event].forEach((eventHandler, index) => {
						if (eventHandler === handler || eventHandler.__emitterProxy && eventHandler.__emitterProxy === handler) {
							self.eventsListeners[event].splice(index, 1);
						}
					});
				}
			});
			return self;
		},
		emit(...args) {
			const self = this;
			if (!self.eventsListeners || self.destroyed) return self;
			if (!self.eventsListeners) return self;
			let events;
			let data;
			let context;
			if (typeof args[0] === 'string' || Array.isArray(args[0])) {
				events = args[0];
				data = args.slice(1, args.length);
				context = self;
			} else {
				events = args[0].events;
				data = args[0].data;
				context = args[0].context || self;
			}
			data.unshift(context);
			const eventsArray = Array.isArray(events) ? events : events.split(' ');
			eventsArray.forEach(event => {
				if (self.eventsAnyListeners && self.eventsAnyListeners.length) {
					self.eventsAnyListeners.forEach(eventHandler => {
						eventHandler.apply(context, [event, ...data]);
					});
				}
				if (self.eventsListeners && self.eventsListeners[event]) {
					self.eventsListeners[event].forEach(eventHandler => {
						eventHandler.apply(context, data);
					});
				}
			});
			return self;
		}
	};

	function updateSize() {
		const swiper = this;
		let width;
		let height;
		const el = swiper.el;
		if (typeof swiper.params.width !== 'undefined' && swiper.params.width !== null) {
			width = swiper.params.width;
		} else {
			width = el.clientWidth;
		}
		if (typeof swiper.params.height !== 'undefined' && swiper.params.height !== null) {
			height = swiper.params.height;
		} else {
			height = el.clientHeight;
		}
		if (width === 0 && swiper.isHorizontal() || height === 0 && swiper.isVertical()) {
			return;
		}

		// Subtract paddings
		width = width - parseInt(elementStyle(el, 'padding-left') || 0, 10) - parseInt(elementStyle(el, 'padding-right') || 0, 10);
		height = height - parseInt(elementStyle(el, 'padding-top') || 0, 10) - parseInt(elementStyle(el, 'padding-bottom') || 0, 10);
		if (Number.isNaN(width)) width = 0;
		if (Number.isNaN(height)) height = 0;
		Object.assign(swiper, {
			width,
			height,
			size: swiper.isHorizontal() ? width : height
		});
	}

	function updateSlides() {
		const swiper = this;
		function getDirectionPropertyValue(node, label) {
			return parseFloat(node.getPropertyValue(swiper.getDirectionLabel(label)) || 0);
		}
		const params = swiper.params;
		const {
			wrapperEl,
			slidesEl,
			rtlTranslate: rtl,
			wrongRTL
		} = swiper;
		const isVirtual = swiper.virtual && params.virtual.enabled;
		const previousSlidesLength = isVirtual ? swiper.virtual.slides.length : swiper.slides.length;
		const slides = elementChildren(slidesEl, `.${swiper.params.slideClass}, swiper-slide`);
		const slidesLength = isVirtual ? swiper.virtual.slides.length : slides.length;
		let snapGrid = [];
		const slidesGrid = [];
		const slidesSizesGrid = [];
		let offsetBefore = params.slidesOffsetBefore;
		if (typeof offsetBefore === 'function') {
			offsetBefore = params.slidesOffsetBefore.call(swiper);
		}
		let offsetAfter = params.slidesOffsetAfter;
		if (typeof offsetAfter === 'function') {
			offsetAfter = params.slidesOffsetAfter.call(swiper);
		}
		const previousSnapGridLength = swiper.snapGrid.length;
		const previousSlidesGridLength = swiper.slidesGrid.length;
		const swiperSize = swiper.size - offsetBefore - offsetAfter;
		let spaceBetween = params.spaceBetween;
		let slidePosition = -offsetBefore;
		let prevSlideSize = 0;
		let index = 0;
		if (typeof swiperSize === 'undefined') {
			return;
		}
		if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
			spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * swiperSize;
		} else if (typeof spaceBetween === 'string') {
			spaceBetween = parseFloat(spaceBetween);
		}
		swiper.virtualSize = -spaceBetween - offsetBefore - offsetAfter;

		// reset margins
		slides.forEach(slideEl => {
			if (rtl) {
				slideEl.style.marginLeft = '';
			} else {
				slideEl.style.marginRight = '';
			}
			slideEl.style.marginBottom = '';
			slideEl.style.marginTop = '';
		});

		// reset cssMode offsets
		if (params.centeredSlides && params.cssMode) {
			setCSSProperty(wrapperEl, '--swiper-centered-offset-before', '');
			setCSSProperty(wrapperEl, '--swiper-centered-offset-after', '');
		}

		// set cssMode offsets
		if (params.cssMode) {
			setCSSProperty(wrapperEl, '--swiper-slides-offset-before', `${offsetBefore}px`);
			setCSSProperty(wrapperEl, '--swiper-slides-offset-after', `${offsetAfter}px`);
		}
		const gridEnabled = params.grid && params.grid.rows > 1 && swiper.grid;
		if (gridEnabled) {
			swiper.grid.initSlides(slides);
		} else if (swiper.grid) {
			swiper.grid.unsetSlides();
		}

		// Calc slides
		let slideSize;
		const shouldResetSlideSize = params.slidesPerView === 'auto' && params.breakpoints && Object.keys(params.breakpoints).filter(key => {
			return typeof params.breakpoints[key].slidesPerView !== 'undefined';
		}).length > 0;
		for (let i = 0; i < slidesLength; i += 1) {
			slideSize = 0;
			const slide = slides[i];
			if (slide) {
				if (gridEnabled) {
					swiper.grid.updateSlide(i, slide, slides);
				}
				if (elementStyle(slide, 'display') === 'none') continue; // eslint-disable-line
			}

			if (isVirtual && params.slidesPerView === 'auto') {
				if (params.virtual.slidesPerViewAutoSlideSize) {
					slideSize = params.virtual.slidesPerViewAutoSlideSize;
				}
				if (slideSize && slide) {
					if (params.roundLengths) slideSize = Math.floor(slideSize);
					slide.style[swiper.getDirectionLabel('width')] = `${slideSize}px`;
				}
			} else if (params.slidesPerView === 'auto') {
				if (shouldResetSlideSize) {
					slide.style[swiper.getDirectionLabel('width')] = ``;
				}
				const slideStyles = getComputedStyle(slide);
				const currentTransform = slide.style.transform;
				const currentWebKitTransform = slide.style.webkitTransform;
				if (currentTransform) {
					slide.style.transform = 'none';
				}
				if (currentWebKitTransform) {
					slide.style.webkitTransform = 'none';
				}
				if (params.roundLengths) {
					slideSize = swiper.isHorizontal() ? elementOuterSize(slide, 'width', true) : elementOuterSize(slide, 'height', true);
				} else {
					// eslint-disable-next-line
					const width = getDirectionPropertyValue(slideStyles, 'width');
					const paddingLeft = getDirectionPropertyValue(slideStyles, 'padding-left');
					const paddingRight = getDirectionPropertyValue(slideStyles, 'padding-right');
					const marginLeft = getDirectionPropertyValue(slideStyles, 'margin-left');
					const marginRight = getDirectionPropertyValue(slideStyles, 'margin-right');
					const boxSizing = slideStyles.getPropertyValue('box-sizing');
					if (boxSizing && boxSizing === 'border-box') {
						slideSize = width + marginLeft + marginRight;
					} else {
						const {
							clientWidth,
							offsetWidth
						} = slide;
						slideSize = width + paddingLeft + paddingRight + marginLeft + marginRight + (offsetWidth - clientWidth);
					}
				}
				if (currentTransform) {
					slide.style.transform = currentTransform;
				}
				if (currentWebKitTransform) {
					slide.style.webkitTransform = currentWebKitTransform;
				}
				if (params.roundLengths) slideSize = Math.floor(slideSize);
			} else {
				slideSize = (swiperSize - (params.slidesPerView - 1) * spaceBetween) / params.slidesPerView;
				if (params.roundLengths) slideSize = Math.floor(slideSize);
				if (slide) {
					slide.style[swiper.getDirectionLabel('width')] = `${slideSize}px`;
				}
			}
			if (slide) {
				slide.swiperSlideSize = slideSize;
			}
			slidesSizesGrid.push(slideSize);
			if (params.centeredSlides) {
				slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween;
				if (prevSlideSize === 0 && i !== 0) slidePosition = slidePosition - swiperSize / 2 - spaceBetween;
				if (i === 0) slidePosition = slidePosition - swiperSize / 2 - spaceBetween;
				if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0;
				if (params.roundLengths) slidePosition = Math.floor(slidePosition);
				if (index % params.slidesPerGroup === 0) snapGrid.push(slidePosition);
				slidesGrid.push(slidePosition);
			} else {
				if (params.roundLengths) slidePosition = Math.floor(slidePosition);
				if ((index - Math.min(swiper.params.slidesPerGroupSkip, index)) % swiper.params.slidesPerGroup === 0) snapGrid.push(slidePosition);
				slidesGrid.push(slidePosition);
				slidePosition = slidePosition + slideSize + spaceBetween;
			}
			swiper.virtualSize += slideSize + spaceBetween;
			prevSlideSize = slideSize;
			index += 1;
		}
		swiper.virtualSize = Math.max(swiper.virtualSize, swiperSize) + offsetAfter;
		if (rtl && wrongRTL && (params.effect === 'slide' || params.effect === 'coverflow')) {
			wrapperEl.style.width = `${swiper.virtualSize + spaceBetween}px`;
		}
		if (params.setWrapperSize) {
			wrapperEl.style[swiper.getDirectionLabel('width')] = `${swiper.virtualSize + spaceBetween}px`;
		}
		if (gridEnabled) {
			swiper.grid.updateWrapperSize(slideSize, snapGrid);
		}

		// Remove last grid elements depending on width
		if (!params.centeredSlides) {
			// Check if snapToSlideEdge should be applied
			const isFractionalSlidesPerView = params.slidesPerView !== 'auto' && params.slidesPerView % 1 !== 0;
			const shouldSnapToSlideEdge = params.snapToSlideEdge && !params.loop && (params.slidesPerView === 'auto' || isFractionalSlidesPerView);

			// Calculate the last allowed snap index when snapToSlideEdge is enabled
			// This ensures minimum slides are visible at the end
			let lastAllowedSnapIndex = snapGrid.length;
			if (shouldSnapToSlideEdge) {
				let minVisibleSlides;
				if (params.slidesPerView === 'auto') {
					// For 'auto' mode, calculate how many slides fit based on actual sizes
					minVisibleSlides = 1;
					let accumulatedSize = 0;
					for (let i = slidesSizesGrid.length - 1; i >= 0; i -= 1) {
						accumulatedSize += slidesSizesGrid[i] + (i < slidesSizesGrid.length - 1 ? spaceBetween : 0);
						if (accumulatedSize <= swiperSize) {
							minVisibleSlides = slidesSizesGrid.length - i;
						} else {
							break;
						}
					}
				} else {
					minVisibleSlides = Math.floor(params.slidesPerView);
				}
				lastAllowedSnapIndex = Math.max(slidesLength - minVisibleSlides, 0);
			}
			const newSlidesGrid = [];
			for (let i = 0; i < snapGrid.length; i += 1) {
				let slidesGridItem = snapGrid[i];
				if (params.roundLengths) slidesGridItem = Math.floor(slidesGridItem);
				if (shouldSnapToSlideEdge) {
					// When snapToSlideEdge is enabled, only keep snaps up to lastAllowedSnapIndex
					if (i <= lastAllowedSnapIndex) {
						newSlidesGrid.push(slidesGridItem);
					}
				} else if (snapGrid[i] <= swiper.virtualSize - swiperSize) {
					// When snapToSlideEdge is disabled, keep snaps that fit within scrollable area
					newSlidesGrid.push(slidesGridItem);
				}
			}
			snapGrid = newSlidesGrid;
			if (Math.floor(swiper.virtualSize - swiperSize) - Math.floor(snapGrid[snapGrid.length - 1]) > 1) {
				// Only add edge-aligned snap if snapToSlideEdge is not enabled
				if (!shouldSnapToSlideEdge) {
					snapGrid.push(swiper.virtualSize - swiperSize);
				}
			}
		}
		if (isVirtual && params.loop) {
			const size = slidesSizesGrid[0] + spaceBetween;
			if (params.slidesPerGroup > 1) {
				const groups = Math.ceil((swiper.virtual.slidesBefore + swiper.virtual.slidesAfter) / params.slidesPerGroup);
				const groupSize = size * params.slidesPerGroup;
				for (let i = 0; i < groups; i += 1) {
					snapGrid.push(snapGrid[snapGrid.length - 1] + groupSize);
				}
			}
			for (let i = 0; i < swiper.virtual.slidesBefore + swiper.virtual.slidesAfter; i += 1) {
				if (params.slidesPerGroup === 1) {
					snapGrid.push(snapGrid[snapGrid.length - 1] + size);
				}
				slidesGrid.push(slidesGrid[slidesGrid.length - 1] + size);
				swiper.virtualSize += size;
			}
		}
		if (snapGrid.length === 0) snapGrid = [0];
		if (spaceBetween !== 0) {
			const key = swiper.isHorizontal() && rtl ? 'marginLeft' : swiper.getDirectionLabel('marginRight');
			slides.filter((_, slideIndex) => {
				if (!params.cssMode || params.loop) return true;
				if (slideIndex === slides.length - 1) {
					return false;
				}
				return true;
			}).forEach(slideEl => {
				slideEl.style[key] = `${spaceBetween}px`;
			});
		}
		if (params.centeredSlides && params.centeredSlidesBounds) {
			let allSlidesSize = 0;
			slidesSizesGrid.forEach(slideSizeValue => {
				allSlidesSize += slideSizeValue + (spaceBetween || 0);
			});
			allSlidesSize -= spaceBetween;
			const maxSnap = allSlidesSize > swiperSize ? allSlidesSize - swiperSize : 0;
			snapGrid = snapGrid.map(snap => {
				if (snap <= 0) return -offsetBefore;
				if (snap > maxSnap) return maxSnap + offsetAfter;
				return snap;
			});
		}
		if (params.centerInsufficientSlides) {
			let allSlidesSize = 0;
			slidesSizesGrid.forEach(slideSizeValue => {
				allSlidesSize += slideSizeValue + (spaceBetween || 0);
			});
			allSlidesSize -= spaceBetween;
			if (allSlidesSize < swiperSize) {
				const allSlidesOffset = (swiperSize - allSlidesSize) / 2;
				snapGrid.forEach((snap, snapIndex) => {
					snapGrid[snapIndex] = snap - allSlidesOffset;
				});
				slidesGrid.forEach((snap, snapIndex) => {
					slidesGrid[snapIndex] = snap + allSlidesOffset;
				});
			}
		}
		Object.assign(swiper, {
			slides,
			snapGrid,
			slidesGrid,
			slidesSizesGrid
		});
		if (params.centeredSlides && params.cssMode && !params.centeredSlidesBounds) {
			setCSSProperty(wrapperEl, '--swiper-centered-offset-before', `${-snapGrid[0]}px`);
			setCSSProperty(wrapperEl, '--swiper-centered-offset-after', `${swiper.size / 2 - slidesSizesGrid[slidesSizesGrid.length - 1] / 2}px`);
			const addToSnapGrid = -swiper.snapGrid[0];
			const addToSlidesGrid = -swiper.slidesGrid[0];
			swiper.snapGrid = swiper.snapGrid.map(v => v + addToSnapGrid);
			swiper.slidesGrid = swiper.slidesGrid.map(v => v + addToSlidesGrid);
		}
		if (slidesLength !== previousSlidesLength) {
			swiper.emit('slidesLengthChange');
		}
		if (snapGrid.length !== previousSnapGridLength) {
			if (swiper.params.watchOverflow) swiper.checkOverflow();
			swiper.emit('snapGridLengthChange');
		}
		if (slidesGrid.length !== previousSlidesGridLength) {
			swiper.emit('slidesGridLengthChange');
		}
		if (params.watchSlidesProgress) {
			swiper.updateSlidesOffset();
		}
		swiper.emit('slidesUpdated');
		if (!isVirtual && !params.cssMode && (params.effect === 'slide' || params.effect === 'fade')) {
			const backFaceHiddenClass = `${params.containerModifierClass}backface-hidden`;
			const hasClassBackfaceClassAdded = swiper.el.classList.contains(backFaceHiddenClass);
			if (slidesLength <= params.maxBackfaceHiddenSlides) {
				if (!hasClassBackfaceClassAdded) swiper.el.classList.add(backFaceHiddenClass);
			} else if (hasClassBackfaceClassAdded) {
				swiper.el.classList.remove(backFaceHiddenClass);
			}
		}
	}

	function updateAutoHeight(speed) {
		const swiper = this;
		const activeSlides = [];
		const isVirtual = swiper.virtual && swiper.params.virtual.enabled;
		let newHeight = 0;
		let i;
		if (typeof speed === 'number') {
			swiper.setTransition(speed);
		} else if (speed === true) {
			swiper.setTransition(swiper.params.speed);
		}
		const getSlideByIndex = index => {
			if (isVirtual) {
				return swiper.slides[swiper.getSlideIndexByData(index)];
			}
			return swiper.slides[index];
		};
		// Find slides currently in view
		if (swiper.params.slidesPerView !== 'auto' && swiper.params.slidesPerView > 1) {
			if (swiper.params.centeredSlides) {
				(swiper.visibleSlides || []).forEach(slide => {
					activeSlides.push(slide);
				});
			} else {
				for (i = 0; i < Math.ceil(swiper.params.slidesPerView); i += 1) {
					const index = swiper.activeIndex + i;
					if (index > swiper.slides.length && !isVirtual) break;
					activeSlides.push(getSlideByIndex(index));
				}
			}
		} else {
			activeSlides.push(getSlideByIndex(swiper.activeIndex));
		}

		// Find new height from highest slide in view
		for (i = 0; i < activeSlides.length; i += 1) {
			if (typeof activeSlides[i] !== 'undefined') {
				const height = activeSlides[i].offsetHeight;
				newHeight = height > newHeight ? height : newHeight;
			}
		}

		// Update Height
		if (newHeight || newHeight === 0) swiper.wrapperEl.style.height = `${newHeight}px`;
	}

	function updateSlidesOffset() {
		const swiper = this;
		const slides = swiper.slides;
		// eslint-disable-next-line
		const minusOffset = swiper.isElement ? swiper.isHorizontal() ? swiper.wrapperEl.offsetLeft : swiper.wrapperEl.offsetTop : 0;
		for (let i = 0; i < slides.length; i += 1) {
			slides[i].swiperSlideOffset = (swiper.isHorizontal() ? slides[i].offsetLeft : slides[i].offsetTop) - minusOffset - swiper.cssOverflowAdjustment();
		}
	}

	const toggleSlideClasses$1 = (slideEl, condition, className) => {
		if (condition && !slideEl.classList.contains(className)) {
			slideEl.classList.add(className);
		} else if (!condition && slideEl.classList.contains(className)) {
			slideEl.classList.remove(className);
		}
	};
	function updateSlidesProgress(translate = this && this.translate || 0) {
		const swiper = this;
		const params = swiper.params;
		const {
			slides,
			rtlTranslate: rtl,
			snapGrid
		} = swiper;
		if (slides.length === 0) return;
		if (typeof slides[0].swiperSlideOffset === 'undefined') swiper.updateSlidesOffset();
		let offsetCenter = -translate;
		if (rtl) offsetCenter = translate;
		swiper.visibleSlidesIndexes = [];
		swiper.visibleSlides = [];
		let spaceBetween = params.spaceBetween;
		if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
			spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * swiper.size;
		} else if (typeof spaceBetween === 'string') {
			spaceBetween = parseFloat(spaceBetween);
		}
		for (let i = 0; i < slides.length; i += 1) {
			const slide = slides[i];
			let slideOffset = slide.swiperSlideOffset;
			if (params.cssMode && params.centeredSlides) {
				slideOffset -= slides[0].swiperSlideOffset;
			}
			const slideProgress = (offsetCenter + (params.centeredSlides ? swiper.minTranslate() : 0) - slideOffset) / (slide.swiperSlideSize + spaceBetween);
			const originalSlideProgress = (offsetCenter - snapGrid[0] + (params.centeredSlides ? swiper.minTranslate() : 0) - slideOffset) / (slide.swiperSlideSize + spaceBetween);
			const slideBefore = -(offsetCenter - slideOffset);
			const slideAfter = slideBefore + swiper.slidesSizesGrid[i];
			const isFullyVisible = slideBefore >= 0 && slideBefore <= swiper.size - swiper.slidesSizesGrid[i];
			const isVisible = slideBefore >= 0 && slideBefore < swiper.size - 1 || slideAfter > 1 && slideAfter <= swiper.size || slideBefore <= 0 && slideAfter >= swiper.size;
			if (isVisible) {
				swiper.visibleSlides.push(slide);
				swiper.visibleSlidesIndexes.push(i);
			}
			toggleSlideClasses$1(slide, isVisible, params.slideVisibleClass);
			toggleSlideClasses$1(slide, isFullyVisible, params.slideFullyVisibleClass);
			slide.progress = rtl ? -slideProgress : slideProgress;
			slide.originalProgress = rtl ? -originalSlideProgress : originalSlideProgress;
		}
	}

	function updateProgress(translate) {
		const swiper = this;
		if (typeof translate === 'undefined') {
			const multiplier = swiper.rtlTranslate ? -1 : 1;
			// eslint-disable-next-line
			translate = swiper && swiper.translate && swiper.translate * multiplier || 0;
		}
		const params = swiper.params;
		const translatesDiff = swiper.maxTranslate() - swiper.minTranslate();
		let {
			progress,
			isBeginning,
			isEnd,
			progressLoop
		} = swiper;
		const wasBeginning = isBeginning;
		const wasEnd = isEnd;
		if (translatesDiff === 0) {
			progress = 0;
			isBeginning = true;
			isEnd = true;
		} else {
			progress = (translate - swiper.minTranslate()) / translatesDiff;
			const isBeginningRounded = Math.abs(translate - swiper.minTranslate()) < 1;
			const isEndRounded = Math.abs(translate - swiper.maxTranslate()) < 1;
			isBeginning = isBeginningRounded || progress <= 0;
			isEnd = isEndRounded || progress >= 1;
			if (isBeginningRounded) progress = 0;
			if (isEndRounded) progress = 1;
		}
		if (params.loop) {
			const firstSlideIndex = swiper.getSlideIndexByData(0);
			const lastSlideIndex = swiper.getSlideIndexByData(swiper.slides.length - 1);
			const firstSlideTranslate = swiper.slidesGrid[firstSlideIndex];
			const lastSlideTranslate = swiper.slidesGrid[lastSlideIndex];
			const translateMax = swiper.slidesGrid[swiper.slidesGrid.length - 1];
			const translateAbs = Math.abs(translate);
			if (translateAbs >= firstSlideTranslate) {
				progressLoop = (translateAbs - firstSlideTranslate) / translateMax;
			} else {
				progressLoop = (translateAbs + translateMax - lastSlideTranslate) / translateMax;
			}
			if (progressLoop > 1) progressLoop -= 1;
		}
		Object.assign(swiper, {
			progress,
			progressLoop,
			isBeginning,
			isEnd
		});
		if (params.watchSlidesProgress || params.centeredSlides && params.autoHeight) swiper.updateSlidesProgress(translate);
		if (isBeginning && !wasBeginning) {
			swiper.emit('reachBeginning toEdge');
		}
		if (isEnd && !wasEnd) {
			swiper.emit('reachEnd toEdge');
		}
		if (wasBeginning && !isBeginning || wasEnd && !isEnd) {
			swiper.emit('fromEdge');
		}
		swiper.emit('progress', progress);
	}

	const toggleSlideClasses = (slideEl, condition, className) => {
		if (condition && !slideEl.classList.contains(className)) {
			slideEl.classList.add(className);
		} else if (!condition && slideEl.classList.contains(className)) {
			slideEl.classList.remove(className);
		}
	};
	function updateSlidesClasses() {
		const swiper = this;
		const {
			slides,
			params,
			slidesEl,
			activeIndex
		} = swiper;
		const isVirtual = swiper.virtual && params.virtual.enabled;
		const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
		const getFilteredSlide = selector => {
			return elementChildren(slidesEl, `.${params.slideClass}${selector}, swiper-slide${selector}`)[0];
		};
		let activeSlide;
		let prevSlide;
		let nextSlide;
		if (isVirtual) {
			if (params.loop) {
				let slideIndex = activeIndex - swiper.virtual.slidesBefore;
				if (slideIndex < 0) slideIndex = swiper.virtual.slides.length + slideIndex;
				if (slideIndex >= swiper.virtual.slides.length) slideIndex -= swiper.virtual.slides.length;
				activeSlide = getFilteredSlide(`[data-swiper-slide-index="${slideIndex}"]`);
			} else {
				activeSlide = getFilteredSlide(`[data-swiper-slide-index="${activeIndex}"]`);
			}
		} else {
			if (gridEnabled) {
				activeSlide = slides.find(slideEl => slideEl.column === activeIndex);
				nextSlide = slides.find(slideEl => slideEl.column === activeIndex + 1);
				prevSlide = slides.find(slideEl => slideEl.column === activeIndex - 1);
			} else {
				activeSlide = slides[activeIndex];
			}
		}
		if (activeSlide) {
			if (!gridEnabled) {
				// Next Slide
				nextSlide = elementNextAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0];
				if (params.loop && !nextSlide) {
					nextSlide = slides[0];
				}

				// Prev Slide
				prevSlide = elementPrevAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0];
				if (params.loop && !prevSlide === 0) {
					prevSlide = slides[slides.length - 1];
				}
			}
		}
		slides.forEach(slideEl => {
			toggleSlideClasses(slideEl, slideEl === activeSlide, params.slideActiveClass);
			toggleSlideClasses(slideEl, slideEl === nextSlide, params.slideNextClass);
			toggleSlideClasses(slideEl, slideEl === prevSlide, params.slidePrevClass);
		});
		swiper.emitSlidesClasses();
	}

	const processLazyPreloader = (swiper, imageEl) => {
		if (!swiper || swiper.destroyed || !swiper.params) return;
		const slideSelector = () => swiper.isElement ? `swiper-slide` : `.${swiper.params.slideClass}`;
		const slideEl = imageEl.closest(slideSelector());
		if (slideEl) {
			let lazyEl = slideEl.querySelector(`.${swiper.params.lazyPreloaderClass}`);
			if (!lazyEl && swiper.isElement) {
				if (slideEl.shadowRoot) {
					lazyEl = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`);
				} else {
					// init later
					requestAnimationFrame(() => {
						if (slideEl.shadowRoot) {
							lazyEl = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`);
							if (lazyEl && !lazyEl.lazyPreloaderManaged) lazyEl.remove();
						}
					});
				}
			}
			// Skip removal if managed by React/Vue component
			if (lazyEl && !lazyEl.lazyPreloaderManaged) lazyEl.remove();
		}
	};
	const unlazy = (swiper, index) => {
		if (!swiper.slides[index]) return;
		const imageEl = swiper.slides[index].querySelector('[loading="lazy"]');
		if (imageEl) imageEl.removeAttribute('loading');
	};
	const preload = swiper => {
		if (!swiper || swiper.destroyed || !swiper.params) return;
		let amount = swiper.params.lazyPreloadPrevNext;
		const len = swiper.slides.length;
		if (!len || !amount || amount < 0) return;
		amount = Math.min(amount, len);
		const slidesPerView = swiper.params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : Math.ceil(swiper.params.slidesPerView);
		const activeIndex = swiper.activeIndex;
		if (swiper.params.grid && swiper.params.grid.rows > 1) {
			const activeColumn = activeIndex;
			const preloadColumns = [activeColumn - amount];
			preloadColumns.push(...Array.from({
				length: amount
			}).map((_, i) => {
				return activeColumn + slidesPerView + i;
			}));
			swiper.slides.forEach((slideEl, i) => {
				if (preloadColumns.includes(slideEl.column)) unlazy(swiper, i);
			});
			return;
		}
		const slideIndexLastInView = activeIndex + slidesPerView - 1;
		if (swiper.params.rewind || swiper.params.loop) {
			for (let i = activeIndex - amount; i <= slideIndexLastInView + amount; i += 1) {
				const realIndex = (i % len + len) % len;
				if (realIndex < activeIndex || realIndex > slideIndexLastInView) unlazy(swiper, realIndex);
			}
		} else {
			for (let i = Math.max(activeIndex - amount, 0); i <= Math.min(slideIndexLastInView + amount, len - 1); i += 1) {
				if (i !== activeIndex && (i > slideIndexLastInView || i < activeIndex)) {
					unlazy(swiper, i);
				}
			}
		}
	};

	function getActiveIndexByTranslate(swiper) {
		const {
			slidesGrid,
			params
		} = swiper;
		const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate;
		let activeIndex;
		for (let i = 0; i < slidesGrid.length; i += 1) {
			if (typeof slidesGrid[i + 1] !== 'undefined') {
				if (translate >= slidesGrid[i] && translate < slidesGrid[i + 1] - (slidesGrid[i + 1] - slidesGrid[i]) / 2) {
					activeIndex = i;
				} else if (translate >= slidesGrid[i] && translate < slidesGrid[i + 1]) {
					activeIndex = i + 1;
				}
			} else if (translate >= slidesGrid[i]) {
				activeIndex = i;
			}
		}
		// Normalize slideIndex
		if (params.normalizeSlideIndex) {
			if (activeIndex < 0 || typeof activeIndex === 'undefined') activeIndex = 0;
		}
		return activeIndex;
	}
	function updateActiveIndex(newActiveIndex) {
		const swiper = this;
		const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate;
		const {
			snapGrid,
			params,
			activeIndex: previousIndex,
			realIndex: previousRealIndex,
			snapIndex: previousSnapIndex
		} = swiper;
		let activeIndex = newActiveIndex;
		let snapIndex;
		const getVirtualRealIndex = aIndex => {
			let realIndex = aIndex - swiper.virtual.slidesBefore;
			if (realIndex < 0) {
				realIndex = swiper.virtual.slides.length + realIndex;
			}
			if (realIndex >= swiper.virtual.slides.length) {
				realIndex -= swiper.virtual.slides.length;
			}
			return realIndex;
		};
		if (typeof activeIndex === 'undefined') {
			activeIndex = getActiveIndexByTranslate(swiper);
		}
		if (snapGrid.indexOf(translate) >= 0) {
			snapIndex = snapGrid.indexOf(translate);
		} else {
			const skip = Math.min(params.slidesPerGroupSkip, activeIndex);
			snapIndex = skip + Math.floor((activeIndex - skip) / params.slidesPerGroup);
		}
		if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1;
		if (activeIndex === previousIndex && !swiper.params.loop) {
			if (snapIndex !== previousSnapIndex) {
				swiper.snapIndex = snapIndex;
				swiper.emit('snapIndexChange');
			}
			return;
		}
		if (activeIndex === previousIndex && swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) {
			swiper.realIndex = getVirtualRealIndex(activeIndex);
			return;
		}
		const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;

		// Get real index
		let realIndex;
		if (swiper.virtual && params.virtual.enabled) {
			if (params.loop) {
				realIndex = getVirtualRealIndex(activeIndex);
			} else {
				realIndex = activeIndex;
			}
		} else if (gridEnabled) {
			const firstSlideInColumn = swiper.slides.find(slideEl => slideEl.column === activeIndex);
			let activeSlideIndex = parseInt(firstSlideInColumn.getAttribute('data-swiper-slide-index'), 10);
			if (Number.isNaN(activeSlideIndex)) {
				activeSlideIndex = Math.max(swiper.slides.indexOf(firstSlideInColumn), 0);
			}
			realIndex = Math.floor(activeSlideIndex / params.grid.rows);
		} else if (swiper.slides[activeIndex]) {
			const slideIndex = swiper.slides[activeIndex].getAttribute('data-swiper-slide-index');
			if (slideIndex) {
				realIndex = parseInt(slideIndex, 10);
			} else {
				realIndex = activeIndex;
			}
		} else {
			realIndex = activeIndex;
		}
		Object.assign(swiper, {
			previousSnapIndex,
			snapIndex,
			previousRealIndex,
			realIndex,
			previousIndex,
			activeIndex
		});
		if (swiper.initialized) {
			preload(swiper);
		}
		swiper.emit('activeIndexChange');
		swiper.emit('snapIndexChange');
		if (swiper.initialized || swiper.params.runCallbacksOnInit) {
			if (previousRealIndex !== realIndex) {
				swiper.emit('realIndexChange');
			}
			swiper.emit('slideChange');
		}
	}

	function updateClickedSlide(el, path) {
		const swiper = this;
		const params = swiper.params;
		let slide = el.closest(`.${params.slideClass}, swiper-slide`);
		if (!slide && swiper.isElement && path && path.length > 1 && path.includes(el)) {
			[...path.slice(path.indexOf(el) + 1, path.length)].forEach(pathEl => {
				if (!slide && pathEl.matches && pathEl.matches(`.${params.slideClass}, swiper-slide`)) {
					slide = pathEl;
				}
			});
		}
		let slideFound = false;
		let slideIndex;
		if (slide) {
			for (let i = 0; i < swiper.slides.length; i += 1) {
				if (swiper.slides[i] === slide) {
					slideFound = true;
					slideIndex = i;
					break;
				}
			}
		}
		if (slide && slideFound) {
			swiper.clickedSlide = slide;
			if (swiper.virtual && swiper.params.virtual.enabled) {
				swiper.clickedIndex = parseInt(slide.getAttribute('data-swiper-slide-index'), 10);
			} else {
				swiper.clickedIndex = slideIndex;
			}
		} else {
			swiper.clickedSlide = undefined;
			swiper.clickedIndex = undefined;
			return;
		}
		if (params.slideToClickedSlide && swiper.clickedIndex !== undefined && swiper.clickedIndex !== swiper.activeIndex) {
			swiper.slideToClickedSlide();
		}
	}

	var update = {
		updateSize,
		updateSlides,
		updateAutoHeight,
		updateSlidesOffset,
		updateSlidesProgress,
		updateProgress,
		updateSlidesClasses,
		updateActiveIndex,
		updateClickedSlide
	};

	function getSwiperTranslate(axis = this.isHorizontal() ? 'x' : 'y') {
		const swiper = this;
		const {
			params,
			rtlTranslate: rtl,
			translate,
			wrapperEl
		} = swiper;
		if (params.virtualTranslate) {
			return rtl ? -translate : translate;
		}
		if (params.cssMode) {
			return translate;
		}
		let currentTranslate = getTranslate(wrapperEl, axis);
		currentTranslate += swiper.cssOverflowAdjustment();
		if (rtl) currentTranslate = -currentTranslate;
		return currentTranslate || 0;
	}

	function setTranslate(translate, byController) {
		const swiper = this;
		const {
			rtlTranslate: rtl,
			params,
			wrapperEl,
			progress
		} = swiper;
		let x = 0;
		let y = 0;
		const z = 0;
		if (swiper.isHorizontal()) {
			x = rtl ? -translate : translate;
		} else {
			y = translate;
		}
		if (params.roundLengths) {
			x = Math.floor(x);
			y = Math.floor(y);
		}
		swiper.previousTranslate = swiper.translate;
		swiper.translate = swiper.isHorizontal() ? x : y;
		if (params.cssMode) {
			wrapperEl[swiper.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = swiper.isHorizontal() ? -x : -y;
		} else if (!params.virtualTranslate) {
			if (swiper.isHorizontal()) {
				x -= swiper.cssOverflowAdjustment();
			} else {
				y -= swiper.cssOverflowAdjustment();
			}
			wrapperEl.style.transform = `translate3d(${x}px, ${y}px, ${z}px)`;
		}

		// Check if we need to update progress
		let newProgress;
		const translatesDiff = swiper.maxTranslate() - swiper.minTranslate();
		if (translatesDiff === 0) {
			newProgress = 0;
		} else {
			newProgress = (translate - swiper.minTranslate()) / translatesDiff;
		}
		if (newProgress !== progress) {
			swiper.updateProgress(translate);
		}
		swiper.emit('setTranslate', swiper.translate, byController);
	}

	function minTranslate() {
		return -this.snapGrid[0];
	}

	function maxTranslate() {
		return -this.snapGrid[this.snapGrid.length - 1];
	}

	function translateTo(translate = 0, speed = this.params.speed, runCallbacks = true, translateBounds = true, internal) {
		const swiper = this;
		const {
			params,
			wrapperEl
		} = swiper;
		if (swiper.animating && params.preventInteractionOnTransition) {
			return false;
		}
		const minTranslate = swiper.minTranslate();
		const maxTranslate = swiper.maxTranslate();
		let newTranslate;
		if (translateBounds && translate > minTranslate) newTranslate = minTranslate;else if (translateBounds && translate < maxTranslate) newTranslate = maxTranslate;else newTranslate = translate;

		// Update progress
		swiper.updateProgress(newTranslate);
		if (params.cssMode) {
			const isH = swiper.isHorizontal();
			if (speed === 0) {
				wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = -newTranslate;
			} else {
				if (!swiper.support.smoothScroll) {
					animateCSSModeScroll({
						swiper,
						targetPosition: -newTranslate,
						side: isH ? 'left' : 'top'
					});
					return true;
				}
				wrapperEl.scrollTo({
					[isH ? 'left' : 'top']: -newTranslate,
					behavior: 'smooth'
				});
			}
			return true;
		}
		if (speed === 0) {
			swiper.setTransition(0);
			swiper.setTranslate(newTranslate);
			if (runCallbacks) {
				swiper.emit('beforeTransitionStart', speed, internal);
				swiper.emit('transitionEnd');
			}
		} else {
			swiper.setTransition(speed);
			swiper.setTranslate(newTranslate);
			if (runCallbacks) {
				swiper.emit('beforeTransitionStart', speed, internal);
				swiper.emit('transitionStart');
			}
			if (!swiper.animating) {
				swiper.animating = true;
				if (!swiper.onTranslateToWrapperTransitionEnd) {
					swiper.onTranslateToWrapperTransitionEnd = function transitionEnd(e) {
						if (!swiper || swiper.destroyed) return;
						if (e.target !== this) return;
						swiper.wrapperEl.removeEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
						swiper.onTranslateToWrapperTransitionEnd = null;
						delete swiper.onTranslateToWrapperTransitionEnd;
						swiper.animating = false;
						if (runCallbacks) {
							swiper.emit('transitionEnd');
						}
					};
				}
				swiper.wrapperEl.addEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
			}
		}
		return true;
	}

	var translate = {
		getTranslate: getSwiperTranslate,
		setTranslate,
		minTranslate,
		maxTranslate,
		translateTo
	};

	function setTransition(duration, byController) {
		const swiper = this;
		if (!swiper.params.cssMode) {
			swiper.wrapperEl.style.transitionDuration = `${duration}ms`;
			swiper.wrapperEl.style.transitionDelay = duration === 0 ? `0ms` : '';
		}
		swiper.emit('setTransition', duration, byController);
	}

	function transitionEmit({
								swiper,
								runCallbacks,
								direction,
								step
							}) {
		const {
			activeIndex,
			previousIndex
		} = swiper;
		let dir = direction;
		if (!dir) {
			if (activeIndex > previousIndex) dir = 'next';else if (activeIndex < previousIndex) dir = 'prev';else dir = 'reset';
		}
		swiper.emit(`transition${step}`);
		if (runCallbacks && dir === 'reset') {
			swiper.emit(`slideResetTransition${step}`);
		} else if (runCallbacks && activeIndex !== previousIndex) {
			swiper.emit(`slideChangeTransition${step}`);
			if (dir === 'next') {
				swiper.emit(`slideNextTransition${step}`);
			} else {
				swiper.emit(`slidePrevTransition${step}`);
			}
		}
	}

	function transitionStart(runCallbacks = true, direction) {
		const swiper = this;
		const {
			params
		} = swiper;
		if (params.cssMode) return;
		if (params.autoHeight) {
			swiper.updateAutoHeight();
		}
		transitionEmit({
			swiper,
			runCallbacks,
			direction,
			step: 'Start'
		});
	}

	function transitionEnd(runCallbacks = true, direction) {
		const swiper = this;
		const {
			params
		} = swiper;
		swiper.animating = false;
		if (params.cssMode) return;
		swiper.setTransition(0);
		transitionEmit({
			swiper,
			runCallbacks,
			direction,
			step: 'End'
		});
	}

	var transition = {
		setTransition,
		transitionStart,
		transitionEnd
	};

	function slideTo(index = 0, speed, runCallbacks = true, internal, initial) {
		if (typeof index === 'string') {
			index = parseInt(index, 10);
		}
		const swiper = this;
		let slideIndex = index;
		if (slideIndex < 0) slideIndex = 0;
		const {
			params,
			snapGrid,
			slidesGrid,
			previousIndex,
			activeIndex,
			rtlTranslate: rtl,
			wrapperEl,
			enabled
		} = swiper;
		if (!enabled && !internal && !initial || swiper.destroyed || swiper.animating && params.preventInteractionOnTransition) {
			return false;
		}
		if (typeof speed === 'undefined') {
			speed = swiper.params.speed;
		}
		const skip = Math.min(swiper.params.slidesPerGroupSkip, slideIndex);
		let snapIndex = skip + Math.floor((slideIndex - skip) / swiper.params.slidesPerGroup);
		if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1;
		const translate = -snapGrid[snapIndex];
		// Normalize slideIndex
		if (params.normalizeSlideIndex) {
			for (let i = 0; i < slidesGrid.length; i += 1) {
				const normalizedTranslate = -Math.floor(translate * 100);
				const normalizedGrid = Math.floor(slidesGrid[i] * 100);
				const normalizedGridNext = Math.floor(slidesGrid[i + 1] * 100);
				if (typeof slidesGrid[i + 1] !== 'undefined') {
					if (normalizedTranslate >= normalizedGrid && normalizedTranslate < normalizedGridNext - (normalizedGridNext - normalizedGrid) / 2) {
						slideIndex = i;
					} else if (normalizedTranslate >= normalizedGrid && normalizedTranslate < normalizedGridNext) {
						slideIndex = i + 1;
					}
				} else if (normalizedTranslate >= normalizedGrid) {
					slideIndex = i;
				}
			}
		}
		// Directions locks
		if (swiper.initialized && slideIndex !== activeIndex) {
			if (!swiper.allowSlideNext && (rtl ? translate > swiper.translate && translate > swiper.minTranslate() : translate < swiper.translate && translate < swiper.minTranslate())) {
				return false;
			}
			if (!swiper.allowSlidePrev && translate > swiper.translate && translate > swiper.maxTranslate()) {
				if ((activeIndex || 0) !== slideIndex) {
					return false;
				}
			}
		}
		if (slideIndex !== (previousIndex || 0) && runCallbacks) {
			swiper.emit('beforeSlideChangeStart');
		}

		// Update progress
		swiper.updateProgress(translate);
		let direction;
		if (slideIndex > activeIndex) direction = 'next';else if (slideIndex < activeIndex) direction = 'prev';else direction = 'reset';

		// initial virtual
		const isVirtual = swiper.virtual && swiper.params.virtual.enabled;
		const isInitialVirtual = isVirtual && initial;
		// Update Index
		if (!isInitialVirtual && (rtl && -translate === swiper.translate || !rtl && translate === swiper.translate)) {
			swiper.updateActiveIndex(slideIndex);
			// Update Height
			if (params.autoHeight) {
				swiper.updateAutoHeight();
			}
			swiper.updateSlidesClasses();
			if (params.effect !== 'slide') {
				swiper.setTranslate(translate);
			}
			if (direction !== 'reset') {
				swiper.transitionStart(runCallbacks, direction);
				swiper.transitionEnd(runCallbacks, direction);
			}
			return false;
		}
		if (params.cssMode) {
			const isH = swiper.isHorizontal();
			const t = rtl ? translate : -translate;
			if (speed === 0) {
				if (isVirtual) {
					swiper.wrapperEl.style.scrollSnapType = 'none';
					swiper._immediateVirtual = true;
				}
				if (isVirtual && !swiper._cssModeVirtualInitialSet && swiper.params.initialSlide > 0) {
					swiper._cssModeVirtualInitialSet = true;
					requestAnimationFrame(() => {
						wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = t;
					});
				} else {
					wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = t;
				}
				if (isVirtual) {
					requestAnimationFrame(() => {
						swiper.wrapperEl.style.scrollSnapType = '';
						swiper._immediateVirtual = false;
					});
				}
			} else {
				if (!swiper.support.smoothScroll) {
					animateCSSModeScroll({
						swiper,
						targetPosition: t,
						side: isH ? 'left' : 'top'
					});
					return true;
				}
				wrapperEl.scrollTo({
					[isH ? 'left' : 'top']: t,
					behavior: 'smooth'
				});
			}
			return true;
		}
		const browser = getBrowser();
		const isSafari = browser.isSafari;
		if (isVirtual && !initial && isSafari && swiper.isElement) {
			swiper.virtual.update(false, false, slideIndex);
		}
		swiper.setTransition(speed);
		swiper.setTranslate(translate);
		swiper.updateActiveIndex(slideIndex);
		swiper.updateSlidesClasses();
		swiper.emit('beforeTransitionStart', speed, internal);
		swiper.transitionStart(runCallbacks, direction);
		if (speed === 0) {
			swiper.transitionEnd(runCallbacks, direction);
		} else if (!swiper.animating) {
			swiper.animating = true;
			if (!swiper.onSlideToWrapperTransitionEnd) {
				swiper.onSlideToWrapperTransitionEnd = function transitionEnd(e) {
					if (!swiper || swiper.destroyed) return;
					if (e.target !== this) return;
					swiper.wrapperEl.removeEventListener('transitionend', swiper.onSlideToWrapperTransitionEnd);
					swiper.onSlideToWrapperTransitionEnd = null;
					delete swiper.onSlideToWrapperTransitionEnd;
					swiper.transitionEnd(runCallbacks, direction);
				};
			}
			swiper.wrapperEl.addEventListener('transitionend', swiper.onSlideToWrapperTransitionEnd);
		}
		return true;
	}

	function slideToLoop(index = 0, speed, runCallbacks = true, internal) {
		if (typeof index === 'string') {
			const indexAsNumber = parseInt(index, 10);
			index = indexAsNumber;
		}
		const swiper = this;
		if (swiper.destroyed) return;
		if (typeof speed === 'undefined') {
			speed = swiper.params.speed;
		}
		const gridEnabled = swiper.grid && swiper.params.grid && swiper.params.grid.rows > 1;
		let newIndex = index;
		if (swiper.params.loop) {
			if (swiper.virtual && swiper.params.virtual.enabled) {
				// eslint-disable-next-line
				newIndex = newIndex + swiper.virtual.slidesBefore;
			} else {
				let targetSlideIndex;
				if (gridEnabled) {
					const slideIndex = newIndex * swiper.params.grid.rows;
					targetSlideIndex = swiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex).column;
				} else {
					targetSlideIndex = swiper.getSlideIndexByData(newIndex);
				}
				const cols = gridEnabled ? Math.ceil(swiper.slides.length / swiper.params.grid.rows) : swiper.slides.length;
				const {
					centeredSlides,
					slidesOffsetBefore,
					slidesOffsetAfter
				} = swiper.params;
				const bothDirections = centeredSlides || !!slidesOffsetBefore || !!slidesOffsetAfter;
				let slidesPerView = swiper.params.slidesPerView;
				if (slidesPerView === 'auto') {
					slidesPerView = swiper.slidesPerViewDynamic();
				} else {
					slidesPerView = Math.ceil(parseFloat(swiper.params.slidesPerView, 10));
					if (bothDirections && slidesPerView % 2 === 0) {
						slidesPerView = slidesPerView + 1;
					}
				}
				let needLoopFix = cols - targetSlideIndex < slidesPerView;
				if (bothDirections) {
					needLoopFix = needLoopFix || targetSlideIndex < Math.ceil(slidesPerView / 2);
				}
				if (internal && bothDirections && swiper.params.slidesPerView !== 'auto' && !gridEnabled) {
					needLoopFix = false;
				}
				if (needLoopFix) {
					const direction = bothDirections ? targetSlideIndex < swiper.activeIndex ? 'prev' : 'next' : targetSlideIndex - swiper.activeIndex - 1 < swiper.params.slidesPerView ? 'next' : 'prev';
					swiper.loopFix({
						direction,
						slideTo: true,
						activeSlideIndex: direction === 'next' ? targetSlideIndex + 1 : targetSlideIndex - cols + 1,
						slideRealIndex: direction === 'next' ? swiper.realIndex : undefined
					});
				}
				if (gridEnabled) {
					const slideIndex = newIndex * swiper.params.grid.rows;
					newIndex = swiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex).column;
				} else {
					newIndex = swiper.getSlideIndexByData(newIndex);
				}
			}
		}
		requestAnimationFrame(() => {
			swiper.slideTo(newIndex, speed, runCallbacks, internal);
		});
		return swiper;
	}

	/* eslint no-unused-vars: "off" */
	function slideNext(speed, runCallbacks = true, internal) {
		const swiper = this;
		const {
			enabled,
			params,
			animating
		} = swiper;
		if (!enabled || swiper.destroyed) return swiper;
		if (typeof speed === 'undefined') {
			speed = swiper.params.speed;
		}
		let perGroup = params.slidesPerGroup;
		if (params.slidesPerView === 'auto' && params.slidesPerGroup === 1 && params.slidesPerGroupAuto) {
			perGroup = Math.max(swiper.slidesPerViewDynamic('current', true), 1);
		}
		const increment = swiper.activeIndex < params.slidesPerGroupSkip ? 1 : perGroup;
		const isVirtual = swiper.virtual && params.virtual.enabled;
		if (params.loop) {
			if (animating && !isVirtual && params.loopPreventsSliding) return false;
			swiper.loopFix({
				direction: 'next'
			});
			// eslint-disable-next-line
			swiper._clientLeft = swiper.wrapperEl.clientLeft;
			if (swiper.activeIndex === swiper.slides.length - 1 && params.cssMode) {
				requestAnimationFrame(() => {
					swiper.slideTo(swiper.activeIndex + increment, speed, runCallbacks, internal);
				});
				return true;
			}
		}
		if (params.rewind && swiper.isEnd) {
			return swiper.slideTo(0, speed, runCallbacks, internal);
		}
		return swiper.slideTo(swiper.activeIndex + increment, speed, runCallbacks, internal);
	}

	/* eslint no-unused-vars: "off" */
	function slidePrev(speed, runCallbacks = true, internal) {
		const swiper = this;
		const {
			params,
			snapGrid,
			slidesGrid,
			rtlTranslate,
			enabled,
			animating
		} = swiper;
		if (!enabled || swiper.destroyed) return swiper;
		if (typeof speed === 'undefined') {
			speed = swiper.params.speed;
		}
		const isVirtual = swiper.virtual && params.virtual.enabled;
		if (params.loop) {
			if (animating && !isVirtual && params.loopPreventsSliding) return false;
			swiper.loopFix({
				direction: 'prev'
			});
			// eslint-disable-next-line
			swiper._clientLeft = swiper.wrapperEl.clientLeft;
		}
		const translate = rtlTranslate ? swiper.translate : -swiper.translate;
		function normalize(val) {
			if (val < 0) return -Math.floor(Math.abs(val));
			return Math.floor(val);
		}
		const normalizedTranslate = normalize(translate);
		const normalizedSnapGrid = snapGrid.map(val => normalize(val));
		const isFreeMode = params.freeMode && params.freeMode.enabled;
		let prevSnap = snapGrid[normalizedSnapGrid.indexOf(normalizedTranslate) - 1];
		if (typeof prevSnap === 'undefined' && (params.cssMode || isFreeMode)) {
			let prevSnapIndex;
			snapGrid.forEach((snap, snapIndex) => {
				if (normalizedTranslate >= snap) {
					// prevSnap = snap;
					prevSnapIndex = snapIndex;
				}
			});
			if (typeof prevSnapIndex !== 'undefined') {
				prevSnap = isFreeMode ? snapGrid[prevSnapIndex] : snapGrid[prevSnapIndex > 0 ? prevSnapIndex - 1 : prevSnapIndex];
			}
		}
		let prevIndex = 0;
		if (typeof prevSnap !== 'undefined') {
			prevIndex = slidesGrid.indexOf(prevSnap);
			if (prevIndex < 0) prevIndex = swiper.activeIndex - 1;
			if (params.slidesPerView === 'auto' && params.slidesPerGroup === 1 && params.slidesPerGroupAuto) {
				prevIndex = prevIndex - swiper.slidesPerViewDynamic('previous', true) + 1;
				prevIndex = Math.max(prevIndex, 0);
			}
		}
		if (params.rewind && swiper.isBeginning) {
			const lastIndex = swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual ? swiper.virtual.slides.length - 1 : swiper.slides.length - 1;
			return swiper.slideTo(lastIndex, speed, runCallbacks, internal);
		} else if (params.loop && swiper.activeIndex === 0 && params.cssMode) {
			requestAnimationFrame(() => {
				swiper.slideTo(prevIndex, speed, runCallbacks, internal);
			});
			return true;
		}
		return swiper.slideTo(prevIndex, speed, runCallbacks, internal);
	}

	/* eslint no-unused-vars: "off" */
	function slideReset(speed, runCallbacks = true, internal) {
		const swiper = this;
		if (swiper.destroyed) return;
		if (typeof speed === 'undefined') {
			speed = swiper.params.speed;
		}
		return swiper.slideTo(swiper.activeIndex, speed, runCallbacks, internal);
	}

	/* eslint no-unused-vars: "off" */
	function slideToClosest(speed, runCallbacks = true, internal, threshold = 0.5) {
		const swiper = this;
		if (swiper.destroyed) return;
		if (typeof speed === 'undefined') {
			speed = swiper.params.speed;
		}
		let index = swiper.activeIndex;
		const skip = Math.min(swiper.params.slidesPerGroupSkip, index);
		const snapIndex = skip + Math.floor((index - skip) / swiper.params.slidesPerGroup);
		const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate;
		if (translate >= swiper.snapGrid[snapIndex]) {
			// The current translate is on or after the current snap index, so the choice
			// is between the current index and the one after it.
			const currentSnap = swiper.snapGrid[snapIndex];
			const nextSnap = swiper.snapGrid[snapIndex + 1];
			if (translate - currentSnap > (nextSnap - currentSnap) * threshold) {
				index += swiper.params.slidesPerGroup;
			}
		} else {
			// The current translate is before the current snap index, so the choice
			// is between the current index and the one before it.
			const prevSnap = swiper.snapGrid[snapIndex - 1];
			const currentSnap = swiper.snapGrid[snapIndex];
			if (translate - prevSnap <= (currentSnap - prevSnap) * threshold) {
				index -= swiper.params.slidesPerGroup;
			}
		}
		index = Math.max(index, 0);
		index = Math.min(index, swiper.slidesGrid.length - 1);
		return swiper.slideTo(index, speed, runCallbacks, internal);
	}

	function slideToClickedSlide() {
		const swiper = this;
		if (swiper.destroyed) return;
		const {
			params,
			slidesEl
		} = swiper;
		const slidesPerView = params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : params.slidesPerView;
		let slideToIndex = swiper.getSlideIndexWhenGrid(swiper.clickedIndex);
		let realIndex;
		const slideSelector = swiper.isElement ? `swiper-slide` : `.${params.slideClass}`;
		const isGrid = swiper.grid && swiper.params.grid && swiper.params.grid.rows > 1;
		if (params.loop) {
			if (swiper.animating) return;
			realIndex = parseInt(swiper.clickedSlide.getAttribute('data-swiper-slide-index'), 10);
			if (params.centeredSlides) {
				swiper.slideToLoop(realIndex);
			} else if (slideToIndex > (isGrid ? (swiper.slides.length - slidesPerView) / 2 - (swiper.params.grid.rows - 1) : swiper.slides.length - slidesPerView)) {
				swiper.loopFix();
				slideToIndex = swiper.getSlideIndex(elementChildren(slidesEl, `${slideSelector}[data-swiper-slide-index="${realIndex}"]`)[0]);
				nextTick(() => {
					swiper.slideTo(slideToIndex);
				});
			} else {
				swiper.slideTo(slideToIndex);
			}
		} else {
			swiper.slideTo(slideToIndex);
		}
	}

	var slide = {
		slideTo,
		slideToLoop,
		slideNext,
		slidePrev,
		slideReset,
		slideToClosest,
		slideToClickedSlide
	};

	function loopCreate(slideRealIndex, initial) {
		const swiper = this;
		const {
			params,
			slidesEl
		} = swiper;
		if (!params.loop || swiper.virtual && swiper.params.virtual.enabled) return;
		const initSlides = () => {
			const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
			slides.forEach((el, index) => {
				el.setAttribute('data-swiper-slide-index', index);
			});
		};
		const clearBlankSlides = () => {
			const slides = elementChildren(slidesEl, `.${params.slideBlankClass}`);
			slides.forEach(el => {
				el.remove();
			});
			if (slides.length > 0) {
				swiper.recalcSlides();
				swiper.updateSlides();
			}
		};
		const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
		if (params.loopAddBlankSlides && (params.slidesPerGroup > 1 || gridEnabled)) {
			clearBlankSlides();
		}
		const slidesPerGroup = params.slidesPerGroup * (gridEnabled ? params.grid.rows : 1);
		const shouldFillGroup = swiper.slides.length % slidesPerGroup !== 0;
		const shouldFillGrid = gridEnabled && swiper.slides.length % params.grid.rows !== 0;
		const addBlankSlides = amountOfSlides => {
			for (let i = 0; i < amountOfSlides; i += 1) {
				const slideEl = swiper.isElement ? createElement('swiper-slide', [params.slideBlankClass]) : createElement('div', [params.slideClass, params.slideBlankClass]);
				swiper.slidesEl.append(slideEl);
			}
		};
		if (shouldFillGroup) {
			if (params.loopAddBlankSlides) {
				const slidesToAdd = slidesPerGroup - swiper.slides.length % slidesPerGroup;
				addBlankSlides(slidesToAdd);
				swiper.recalcSlides();
				swiper.updateSlides();
			} else {
				showWarning('Swiper Loop Warning: The number of slides is not even to slidesPerGroup, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)');
			}
			initSlides();
		} else if (shouldFillGrid) {
			if (params.loopAddBlankSlides) {
				const slidesToAdd = params.grid.rows - swiper.slides.length % params.grid.rows;
				addBlankSlides(slidesToAdd);
				swiper.recalcSlides();
				swiper.updateSlides();
			} else {
				showWarning('Swiper Loop Warning: The number of slides is not even to grid.rows, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)');
			}
			initSlides();
		} else {
			initSlides();
		}
		const bothDirections = params.centeredSlides || !!params.slidesOffsetBefore || !!params.slidesOffsetAfter;
		swiper.loopFix({
			slideRealIndex,
			direction: bothDirections ? undefined : 'next',
			initial
		});
	}

	function loopFix({
						 slideRealIndex,
						 slideTo = true,
						 direction,
						 setTranslate,
						 activeSlideIndex,
						 initial,
						 byController,
						 byMousewheel
					 } = {}) {
		const swiper = this;
		if (!swiper.params.loop) return;
		swiper.emit('beforeLoopFix');
		const {
			slides,
			allowSlidePrev,
			allowSlideNext,
			slidesEl,
			params
		} = swiper;
		const {
			centeredSlides,
			slidesOffsetBefore,
			slidesOffsetAfter,
			initialSlide
		} = params;
		const bothDirections = centeredSlides || !!slidesOffsetBefore || !!slidesOffsetAfter;
		swiper.allowSlidePrev = true;
		swiper.allowSlideNext = true;
		if (swiper.virtual && params.virtual.enabled) {
			if (slideTo) {
				if (!bothDirections && swiper.snapIndex === 0) {
					swiper.slideTo(swiper.virtual.slides.length, 0, false, true);
				} else if (bothDirections && swiper.snapIndex < params.slidesPerView) {
					swiper.slideTo(swiper.virtual.slides.length + swiper.snapIndex, 0, false, true);
				} else if (swiper.snapIndex === swiper.snapGrid.length - 1) {
					swiper.slideTo(swiper.virtual.slidesBefore, 0, false, true);
				}
			}
			swiper.allowSlidePrev = allowSlidePrev;
			swiper.allowSlideNext = allowSlideNext;
			swiper.emit('loopFix');
			return;
		}
		let slidesPerView = params.slidesPerView;
		if (slidesPerView === 'auto') {
			slidesPerView = swiper.slidesPerViewDynamic();
		} else {
			slidesPerView = Math.ceil(parseFloat(params.slidesPerView, 10));
			if (bothDirections && slidesPerView % 2 === 0) {
				slidesPerView = slidesPerView + 1;
			}
		}
		const slidesPerGroup = params.slidesPerGroupAuto ? slidesPerView : params.slidesPerGroup;
		let loopedSlides = bothDirections ? Math.max(slidesPerGroup, Math.ceil(slidesPerView / 2)) : slidesPerGroup;
		if (loopedSlides % slidesPerGroup !== 0) {
			loopedSlides += slidesPerGroup - loopedSlides % slidesPerGroup;
		}
		loopedSlides += params.loopAdditionalSlides;
		swiper.loopedSlides = loopedSlides;
		const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
		if (slides.length < slidesPerView + loopedSlides || swiper.params.effect === 'cards' && slides.length < slidesPerView + loopedSlides * 2) {
			showWarning('Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled or not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters');
		} else if (gridEnabled && params.grid.fill === 'row') {
			showWarning('Swiper Loop Warning: Loop mode is not compatible with grid.fill = `row`');
		}
		const prependSlidesIndexes = [];
		const appendSlidesIndexes = [];
		const cols = gridEnabled ? Math.ceil(slides.length / params.grid.rows) : slides.length;
		const isInitialOverflow = initial && cols - initialSlide < slidesPerView && !bothDirections;
		let activeIndex = isInitialOverflow ? initialSlide : swiper.activeIndex;
		if (typeof activeSlideIndex === 'undefined') {
			activeSlideIndex = swiper.getSlideIndex(slides.find(el => el.classList.contains(params.slideActiveClass)));
		} else {
			activeIndex = activeSlideIndex;
		}
		const isNext = direction === 'next' || !direction;
		const isPrev = direction === 'prev' || !direction;
		let slidesPrepended = 0;
		let slidesAppended = 0;
		const activeColIndex = gridEnabled ? slides[activeSlideIndex].column : activeSlideIndex;
		const activeColIndexWithShift = activeColIndex + (bothDirections && typeof setTranslate === 'undefined' ? -slidesPerView / 2 + 0.5 : 0);
		// prepend last slides before start
		if (activeColIndexWithShift < loopedSlides) {
			slidesPrepended = Math.max(loopedSlides - activeColIndexWithShift, slidesPerGroup);
			for (let i = 0; i < loopedSlides - activeColIndexWithShift; i += 1) {
				const index = i - Math.floor(i / cols) * cols;
				if (gridEnabled) {
					const colIndexToPrepend = cols - index - 1;
					for (let i = slides.length - 1; i >= 0; i -= 1) {
						if (slides[i].column === colIndexToPrepend) prependSlidesIndexes.push(i);
					}
					// slides.forEach((slide, slideIndex) => {
					//   if (slide.column === colIndexToPrepend) prependSlidesIndexes.push(slideIndex);
					// });
				} else {
					prependSlidesIndexes.push(cols - index - 1);
				}
			}
		} else if (activeColIndexWithShift + slidesPerView > cols - loopedSlides) {
			slidesAppended = Math.max(activeColIndexWithShift - (cols - loopedSlides * 2), slidesPerGroup);
			if (isInitialOverflow) {
				slidesAppended = Math.max(slidesAppended, slidesPerView - cols + initialSlide + 1);
			}
			for (let i = 0; i < slidesAppended; i += 1) {
				const index = i - Math.floor(i / cols) * cols;
				if (gridEnabled) {
					slides.forEach((slide, slideIndex) => {
						if (slide.column === index) appendSlidesIndexes.push(slideIndex);
					});
				} else {
					appendSlidesIndexes.push(index);
				}
			}
		}
		swiper.__preventObserver__ = true;
		requestAnimationFrame(() => {
			swiper.__preventObserver__ = false;
		});
		if (swiper.params.effect === 'cards' && slides.length < slidesPerView + loopedSlides * 2) {
			if (appendSlidesIndexes.includes(activeSlideIndex)) {
				appendSlidesIndexes.splice(appendSlidesIndexes.indexOf(activeSlideIndex), 1);
			}
			if (prependSlidesIndexes.includes(activeSlideIndex)) {
				prependSlidesIndexes.splice(prependSlidesIndexes.indexOf(activeSlideIndex), 1);
			}
		}
		if (isPrev) {
			prependSlidesIndexes.forEach(index => {
				slides[index].swiperLoopMoveDOM = true;
				slidesEl.prepend(slides[index]);
				slides[index].swiperLoopMoveDOM = false;
			});
		}
		if (isNext) {
			appendSlidesIndexes.forEach(index => {
				slides[index].swiperLoopMoveDOM = true;
				slidesEl.append(slides[index]);
				slides[index].swiperLoopMoveDOM = false;
			});
		}
		swiper.recalcSlides();
		if (params.slidesPerView === 'auto') {
			swiper.updateSlides();
		} else if (gridEnabled && (prependSlidesIndexes.length > 0 && isPrev || appendSlidesIndexes.length > 0 && isNext)) {
			swiper.slides.forEach((slide, slideIndex) => {
				swiper.grid.updateSlide(slideIndex, slide, swiper.slides);
			});
		}
		if (params.watchSlidesProgress) {
			swiper.updateSlidesOffset();
		}
		if (slideTo) {
			if (prependSlidesIndexes.length > 0 && isPrev) {
				if (typeof slideRealIndex === 'undefined') {
					const currentSlideTranslate = swiper.slidesGrid[activeIndex];
					const newSlideTranslate = swiper.slidesGrid[activeIndex + slidesPrepended];
					const diff = newSlideTranslate - currentSlideTranslate;
					if (byMousewheel) {
						swiper.setTranslate(swiper.translate - diff);
					} else {
						swiper.slideTo(activeIndex + Math.ceil(slidesPrepended), 0, false, true);
						if (setTranslate) {
							swiper.touchEventsData.startTranslate = swiper.touchEventsData.startTranslate - diff;
							swiper.touchEventsData.currentTranslate = swiper.touchEventsData.currentTranslate - diff;
						}
					}
				} else {
					if (setTranslate) {
						const shift = gridEnabled ? prependSlidesIndexes.length / params.grid.rows : prependSlidesIndexes.length;
						swiper.slideTo(swiper.activeIndex + shift, 0, false, true);
						swiper.touchEventsData.currentTranslate = swiper.translate;
					}
				}
			} else if (appendSlidesIndexes.length > 0 && isNext) {
				if (typeof slideRealIndex === 'undefined') {
					const currentSlideTranslate = swiper.slidesGrid[activeIndex];
					const newSlideTranslate = swiper.slidesGrid[activeIndex - slidesAppended];
					const diff = newSlideTranslate - currentSlideTranslate;
					if (byMousewheel) {
						swiper.setTranslate(swiper.translate - diff);
					} else {
						swiper.slideTo(activeIndex - slidesAppended, 0, false, true);
						if (setTranslate) {
							swiper.touchEventsData.startTranslate = swiper.touchEventsData.startTranslate - diff;
							swiper.touchEventsData.currentTranslate = swiper.touchEventsData.currentTranslate - diff;
						}
					}
				} else {
					const shift = gridEnabled ? appendSlidesIndexes.length / params.grid.rows : appendSlidesIndexes.length;
					swiper.slideTo(swiper.activeIndex - shift, 0, false, true);
				}
			}
		}
		swiper.allowSlidePrev = allowSlidePrev;
		swiper.allowSlideNext = allowSlideNext;
		if (swiper.controller && swiper.controller.control && !byController) {
			const loopParams = {
				slideRealIndex,
				direction,
				setTranslate,
				activeSlideIndex,
				byController: true
			};
			if (Array.isArray(swiper.controller.control)) {
				swiper.controller.control.forEach(c => {
					if (!c.destroyed && c.params.loop) c.loopFix({
						...loopParams,
						slideTo: c.params.slidesPerView === params.slidesPerView ? slideTo : false
					});
				});
			} else if (swiper.controller.control instanceof swiper.constructor && swiper.controller.control.params.loop) {
				swiper.controller.control.loopFix({
					...loopParams,
					slideTo: swiper.controller.control.params.slidesPerView === params.slidesPerView ? slideTo : false
				});
			}
		}
		swiper.emit('loopFix');
	}

	function loopDestroy() {
		const swiper = this;
		const {
			params,
			slidesEl
		} = swiper;
		if (!params.loop || !slidesEl || swiper.virtual && swiper.params.virtual.enabled) return;
		swiper.recalcSlides();
		const newSlidesOrder = [];
		swiper.slides.forEach(slideEl => {
			const index = typeof slideEl.swiperSlideIndex === 'undefined' ? slideEl.getAttribute('data-swiper-slide-index') * 1 : slideEl.swiperSlideIndex;
			newSlidesOrder[index] = slideEl;
		});
		swiper.slides.forEach(slideEl => {
			slideEl.removeAttribute('data-swiper-slide-index');
		});
		newSlidesOrder.forEach(slideEl => {
			slidesEl.append(slideEl);
		});
		swiper.recalcSlides();
		swiper.slideTo(swiper.realIndex, 0);
	}

	var loop = {
		loopCreate,
		loopFix,
		loopDestroy
	};

	function setGrabCursor(moving) {
		const swiper = this;
		if (!swiper.params.simulateTouch || swiper.params.watchOverflow && swiper.isLocked || swiper.params.cssMode) return;
		const el = swiper.params.touchEventsTarget === 'container' ? swiper.el : swiper.wrapperEl;
		if (swiper.isElement) {
			swiper.__preventObserver__ = true;
		}
		el.style.cursor = 'move';
		el.style.cursor = moving ? 'grabbing' : 'grab';
		if (swiper.isElement) {
			requestAnimationFrame(() => {
				swiper.__preventObserver__ = false;
			});
		}
	}

	function unsetGrabCursor() {
		const swiper = this;
		if (swiper.params.watchOverflow && swiper.isLocked || swiper.params.cssMode) {
			return;
		}
		if (swiper.isElement) {
			swiper.__preventObserver__ = true;
		}
		swiper[swiper.params.touchEventsTarget === 'container' ? 'el' : 'wrapperEl'].style.cursor = '';
		if (swiper.isElement) {
			requestAnimationFrame(() => {
				swiper.__preventObserver__ = false;
			});
		}
	}

	var grabCursor = {
		setGrabCursor,
		unsetGrabCursor
	};

	// Modified from https://stackoverflow.com/questions/54520554/custom-element-getrootnode-closest-function-crossing-multiple-parent-shadowd
	function closestElement(selector, base = this) {
		function __closestFrom(el) {
			if (!el || el === getDocument() || el === getWindow()) return null;
			if (el.assignedSlot) el = el.assignedSlot;
			const found = el.closest(selector);
			if (!found && !el.getRootNode) {
				return null;
			}
			return found || __closestFrom(el.getRootNode().host);
		}
		return __closestFrom(base);
	}
	function preventEdgeSwipe(swiper, event, startX) {
		const window = getWindow();
		const {
			params
		} = swiper;
		const edgeSwipeDetection = params.edgeSwipeDetection;
		const edgeSwipeThreshold = params.edgeSwipeThreshold;
		if (edgeSwipeDetection && (startX <= edgeSwipeThreshold || startX >= window.innerWidth - edgeSwipeThreshold)) {
			if (edgeSwipeDetection === 'prevent') {
				event.preventDefault();
				return true;
			}
			return false;
		}
		return true;
	}
	function onTouchStart(event) {
		const swiper = this;
		const document = getDocument();
		let e = event;
		if (e.originalEvent) e = e.originalEvent;
		const data = swiper.touchEventsData;
		if (e.type === 'pointerdown') {
			if (data.pointerId !== null && data.pointerId !== e.pointerId) {
				return;
			}
			data.pointerId = e.pointerId;
		} else if (e.type === 'touchstart' && e.targetTouches.length === 1) {
			data.touchId = e.targetTouches[0].identifier;
		}
		if (e.type === 'touchstart') {
			// don't proceed touch event
			preventEdgeSwipe(swiper, e, e.targetTouches[0].pageX);
			return;
		}
		const {
			params,
			touches,
			enabled
		} = swiper;
		if (!enabled) return;
		if (!params.simulateTouch && e.pointerType === 'mouse') return;
		if (swiper.animating && params.preventInteractionOnTransition) {
			return;
		}
		if (!swiper.animating && params.cssMode && params.loop) {
			swiper.loopFix();
		}
		let targetEl = e.target;
		if (params.touchEventsTarget === 'wrapper') {
			if (!elementIsChildOf(targetEl, swiper.wrapperEl)) return;
		}
		if ('which' in e && e.which === 3) return;
		if ('button' in e && e.button > 0) return;
		if (data.isTouched && data.isMoved) return;

		// change target el for shadow root component
		const swipingClassHasValue = !!params.noSwipingClass && params.noSwipingClass !== '';
		// eslint-disable-next-line
		const eventPath = e.composedPath ? e.composedPath() : e.path;
		if (swipingClassHasValue && e.target && e.target.shadowRoot && eventPath) {
			targetEl = eventPath[0];
		}
		const noSwipingSelector = params.noSwipingSelector ? params.noSwipingSelector : `.${params.noSwipingClass}`;
		const isTargetShadow = !!(e.target && e.target.shadowRoot);

		// use closestElement for shadow root element to get the actual closest for nested shadow root element
		if (params.noSwiping && (isTargetShadow ? closestElement(noSwipingSelector, targetEl) : targetEl.closest(noSwipingSelector))) {
			swiper.allowClick = true;
			return;
		}
		if (params.swipeHandler) {
			if (!targetEl.closest(params.swipeHandler)) return;
		}
		touches.currentX = e.pageX;
		touches.currentY = e.pageY;
		const startX = touches.currentX;
		const startY = touches.currentY;

		// Do NOT start if iOS edge swipe is detected. Otherwise iOS app cannot swipe-to-go-back anymore

		if (!preventEdgeSwipe(swiper, e, startX)) {
			return;
		}
		Object.assign(data, {
			isTouched: true,
			isMoved: false,
			allowTouchCallbacks: true,
			isScrolling: undefined,
			startMoving: undefined
		});
		touches.startX = startX;
		touches.startY = startY;
		data.touchStartTime = now();
		swiper.allowClick = true;
		swiper.updateSize();
		swiper.swipeDirection = undefined;
		if (params.threshold > 0) data.allowThresholdMove = false;
		let preventDefault = true;
		if (targetEl.matches(data.focusableElements)) {
			preventDefault = false;
			if (targetEl.nodeName === 'SELECT') {
				data.isTouched = false;
			}
		}
		if (document.activeElement && document.activeElement.matches(data.focusableElements) && document.activeElement !== targetEl && (e.pointerType === 'mouse' || e.pointerType !== 'mouse' && !targetEl.matches(data.focusableElements))) {
			document.activeElement.blur();
		}
		const shouldPreventDefault = preventDefault && swiper.allowTouchMove && params.touchStartPreventDefault;
		if ((params.touchStartForcePreventDefault || shouldPreventDefault) && !targetEl.isContentEditable) {
			e.preventDefault();
		}
		if (params.freeMode && params.freeMode.enabled && swiper.freeMode && swiper.animating && !params.cssMode) {
			swiper.freeMode.onTouchStart();
		}
		swiper.emit('touchStart', e);
	}

	function onTouchMove(event) {
		const document = getDocument();
		const swiper = this;
		const data = swiper.touchEventsData;
		const {
			params,
			touches,
			rtlTranslate: rtl,
			enabled
		} = swiper;
		if (!enabled) return;
		if (!params.simulateTouch && event.pointerType === 'mouse') return;
		let e = event;
		if (e.originalEvent) e = e.originalEvent;
		if (e.type === 'pointermove') {
			if (data.touchId !== null) return; // return from pointer if we use touch
			const id = e.pointerId;
			if (id !== data.pointerId) return;
		}
		let targetTouch;
		if (e.type === 'touchmove') {
			targetTouch = [...e.changedTouches].find(t => t.identifier === data.touchId);
			if (!targetTouch || targetTouch.identifier !== data.touchId) return;
		} else {
			targetTouch = e;
		}
		if (!data.isTouched) {
			if (data.startMoving && data.isScrolling) {
				swiper.emit('touchMoveOpposite', e);
			}
			return;
		}
		const pageX = targetTouch.pageX;
		const pageY = targetTouch.pageY;
		if (e.preventedByNestedSwiper) {
			touches.startX = pageX;
			touches.startY = pageY;
			return;
		}
		if (!swiper.allowTouchMove) {
			if (!e.target.matches(data.focusableElements)) {
				swiper.allowClick = false;
			}
			if (data.isTouched) {
				Object.assign(touches, {
					startX: pageX,
					startY: pageY,
					currentX: pageX,
					currentY: pageY
				});
				data.touchStartTime = now();
			}
			return;
		}
		if (params.touchReleaseOnEdges && !params.loop) {
			if (swiper.isVertical()) {
				// Vertical
				if (pageY < touches.startY && swiper.translate <= swiper.maxTranslate() || pageY > touches.startY && swiper.translate >= swiper.minTranslate()) {
					data.isTouched = false;
					data.isMoved = false;
					return;
				}
			} else if (rtl && (pageX > touches.startX && -swiper.translate <= swiper.maxTranslate() || pageX < touches.startX && -swiper.translate >= swiper.minTranslate())) {
				return;
			} else if (!rtl && (pageX < touches.startX && swiper.translate <= swiper.maxTranslate() || pageX > touches.startX && swiper.translate >= swiper.minTranslate())) {
				return;
			}
		}
		if (document.activeElement && document.activeElement.matches(data.focusableElements) && document.activeElement !== e.target && e.pointerType !== 'mouse') {
			document.activeElement.blur();
		}
		if (document.activeElement) {
			if (e.target === document.activeElement && e.target.matches(data.focusableElements)) {
				data.isMoved = true;
				swiper.allowClick = false;
				return;
			}
		}
		if (data.allowTouchCallbacks) {
			swiper.emit('touchMove', e);
		}
		touches.previousX = touches.currentX;
		touches.previousY = touches.currentY;
		touches.currentX = pageX;
		touches.currentY = pageY;
		const diffX = touches.currentX - touches.startX;
		const diffY = touches.currentY - touches.startY;
		if (swiper.params.threshold && Math.sqrt(diffX ** 2 + diffY ** 2) < swiper.params.threshold) return;
		if (typeof data.isScrolling === 'undefined') {
			let touchAngle;
			if (swiper.isHorizontal() && touches.currentY === touches.startY || swiper.isVertical() && touches.currentX === touches.startX) {
				data.isScrolling = false;
			} else {
				// eslint-disable-next-line
				if (diffX * diffX + diffY * diffY >= 25) {
					touchAngle = Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180 / Math.PI;
					data.isScrolling = swiper.isHorizontal() ? touchAngle > params.touchAngle : 90 - touchAngle > params.touchAngle;
				}
			}
		}
		if (data.isScrolling) {
			swiper.emit('touchMoveOpposite', e);
		}
		if (typeof data.startMoving === 'undefined') {
			if (touches.currentX !== touches.startX || touches.currentY !== touches.startY) {
				data.startMoving = true;
			}
		}
		if (data.isScrolling || e.type === 'touchmove' && data.preventTouchMoveFromPointerMove) {
			data.isTouched = false;
			return;
		}
		if (!data.startMoving) {
			return;
		}
		swiper.allowClick = false;
		if (!params.cssMode && e.cancelable) {
			e.preventDefault();
		}
		if (params.touchMoveStopPropagation && !params.nested) {
			e.stopPropagation();
		}
		let diff = swiper.isHorizontal() ? diffX : diffY;
		let touchesDiff = swiper.isHorizontal() ? touches.currentX - touches.previousX : touches.currentY - touches.previousY;
		if (params.oneWayMovement) {
			diff = Math.abs(diff) * (rtl ? 1 : -1);
			touchesDiff = Math.abs(touchesDiff) * (rtl ? 1 : -1);
		}
		touches.diff = diff;
		diff *= params.touchRatio;
		if (rtl) {
			diff = -diff;
			touchesDiff = -touchesDiff;
		}
		const prevTouchesDirection = swiper.touchesDirection;
		swiper.swipeDirection = diff > 0 ? 'prev' : 'next';
		swiper.touchesDirection = touchesDiff > 0 ? 'prev' : 'next';
		const isLoop = swiper.params.loop && !params.cssMode;
		const allowLoopFix = swiper.touchesDirection === 'next' && swiper.allowSlideNext || swiper.touchesDirection === 'prev' && swiper.allowSlidePrev;
		if (!data.isMoved) {
			if (isLoop && allowLoopFix) {
				swiper.loopFix({
					direction: swiper.swipeDirection
				});
			}
			data.startTranslate = swiper.getTranslate();
			swiper.setTransition(0);
			if (swiper.animating) {
				const evt = new window.CustomEvent('transitionend', {
					bubbles: true,
					cancelable: true,
					detail: {
						bySwiperTouchMove: true
					}
				});
				swiper.wrapperEl.dispatchEvent(evt);
			}
			data.allowMomentumBounce = false;
			// Grab Cursor
			if (params.grabCursor && (swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) {
				swiper.setGrabCursor(true);
			}
			swiper.emit('sliderFirstMove', e);
		}
		let loopFixed;
		new Date().getTime();
		if (params._loopSwapReset !== false && data.isMoved && data.allowThresholdMove && prevTouchesDirection !== swiper.touchesDirection && isLoop && allowLoopFix && Math.abs(diff) >= 1) {
			Object.assign(touches, {
				startX: pageX,
				startY: pageY,
				currentX: pageX,
				currentY: pageY,
				startTranslate: data.currentTranslate
			});
			data.loopSwapReset = true;
			data.startTranslate = data.currentTranslate;
			return;
		}
		swiper.emit('sliderMove', e);
		data.isMoved = true;
		data.currentTranslate = diff + data.startTranslate;
		let disableParentSwiper = true;
		let resistanceRatio = params.resistanceRatio;
		if (params.touchReleaseOnEdges) {
			resistanceRatio = 0;
		}
		if (diff > 0) {
			if (isLoop && allowLoopFix && !loopFixed && data.allowThresholdMove && data.currentTranslate > (params.centeredSlides ? swiper.minTranslate() - swiper.slidesSizesGrid[swiper.activeIndex + 1] - (params.slidesPerView !== 'auto' && swiper.slides.length - params.slidesPerView >= 2 ? swiper.slidesSizesGrid[swiper.activeIndex + 1] + swiper.params.spaceBetween : 0) - swiper.params.spaceBetween : swiper.minTranslate())) {
				swiper.loopFix({
					direction: 'prev',
					setTranslate: true,
					activeSlideIndex: 0
				});
			}
			if (data.currentTranslate > swiper.minTranslate()) {
				disableParentSwiper = false;
				if (params.resistance) {
					data.currentTranslate = swiper.minTranslate() - 1 + (-swiper.minTranslate() + data.startTranslate + diff) ** resistanceRatio;
				}
			}
		} else if (diff < 0) {
			if (isLoop && allowLoopFix && !loopFixed && data.allowThresholdMove && data.currentTranslate < (params.centeredSlides ? swiper.maxTranslate() + swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] + swiper.params.spaceBetween + (params.slidesPerView !== 'auto' && swiper.slides.length - params.slidesPerView >= 2 ? swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] + swiper.params.spaceBetween : 0) : swiper.maxTranslate())) {
				swiper.loopFix({
					direction: 'next',
					setTranslate: true,
					activeSlideIndex: swiper.slides.length - (params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : Math.ceil(parseFloat(params.slidesPerView, 10)))
				});
			}
			if (data.currentTranslate < swiper.maxTranslate()) {
				disableParentSwiper = false;
				if (params.resistance) {
					data.currentTranslate = swiper.maxTranslate() + 1 - (swiper.maxTranslate() - data.startTranslate - diff) ** resistanceRatio;
				}
			}
		}
		if (disableParentSwiper) {
			e.preventedByNestedSwiper = true;
		}

		// Directions locks
		if (!swiper.allowSlideNext && swiper.swipeDirection === 'next' && data.currentTranslate < data.startTranslate) {
			data.currentTranslate = data.startTranslate;
		}
		if (!swiper.allowSlidePrev && swiper.swipeDirection === 'prev' && data.currentTranslate > data.startTranslate) {
			data.currentTranslate = data.startTranslate;
		}
		if (!swiper.allowSlidePrev && !swiper.allowSlideNext) {
			data.currentTranslate = data.startTranslate;
		}

		// Threshold
		if (params.threshold > 0) {
			if (Math.abs(diff) > params.threshold || data.allowThresholdMove) {
				if (!data.allowThresholdMove) {
					data.allowThresholdMove = true;
					touches.startX = touches.currentX;
					touches.startY = touches.currentY;
					data.currentTranslate = data.startTranslate;
					touches.diff = swiper.isHorizontal() ? touches.currentX - touches.startX : touches.currentY - touches.startY;
					return;
				}
			} else {
				data.currentTranslate = data.startTranslate;
				return;
			}
		}
		if (!params.followFinger || params.cssMode) return;

		// Update active index in free mode
		if (params.freeMode && params.freeMode.enabled && swiper.freeMode || params.watchSlidesProgress) {
			swiper.updateActiveIndex();
			swiper.updateSlidesClasses();
		}
		if (params.freeMode && params.freeMode.enabled && swiper.freeMode) {
			swiper.freeMode.onTouchMove();
		}
		// Update progress
		swiper.updateProgress(data.currentTranslate);
		// Update translate
		swiper.setTranslate(data.currentTranslate);
	}

	function onTouchEnd(event) {
		const swiper = this;
		const data = swiper.touchEventsData;
		let e = event;
		if (e.originalEvent) e = e.originalEvent;
		let targetTouch;
		const isTouchEvent = e.type === 'touchend' || e.type === 'touchcancel';
		if (!isTouchEvent) {
			if (data.touchId !== null) return; // return from pointer if we use touch
			if (e.pointerId !== data.pointerId) return;
			targetTouch = e;
		} else {
			targetTouch = [...e.changedTouches].find(t => t.identifier === data.touchId);
			if (!targetTouch || targetTouch.identifier !== data.touchId) return;
		}
		if (['pointercancel', 'pointerout', 'pointerleave', 'contextmenu'].includes(e.type)) {
			const proceed = ['pointercancel', 'contextmenu'].includes(e.type) && (swiper.browser.isSafari || swiper.browser.isWebView);
			if (!proceed) {
				return;
			}
		}
		data.pointerId = null;
		data.touchId = null;
		const {
			params,
			touches,
			rtlTranslate: rtl,
			slidesGrid,
			enabled
		} = swiper;
		if (!enabled) return;
		if (!params.simulateTouch && e.pointerType === 'mouse') return;
		if (data.allowTouchCallbacks) {
			swiper.emit('touchEnd', e);
		}
		data.allowTouchCallbacks = false;
		if (!data.isTouched) {
			if (data.isMoved && params.grabCursor) {
				swiper.setGrabCursor(false);
			}
			data.isMoved = false;
			data.startMoving = false;
			return;
		}

		// Return Grab Cursor
		if (params.grabCursor && data.isMoved && data.isTouched && (swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) {
			swiper.setGrabCursor(false);
		}

		// Time diff
		const touchEndTime = now();
		const timeDiff = touchEndTime - data.touchStartTime;

		// Tap, doubleTap, Click
		if (swiper.allowClick) {
			const pathTree = e.path || e.composedPath && e.composedPath();
			swiper.updateClickedSlide(pathTree && pathTree[0] || e.target, pathTree);
			swiper.emit('tap click', e);
			if (timeDiff < 300 && touchEndTime - data.lastClickTime < 300) {
				swiper.emit('doubleTap doubleClick', e);
			}
		}
		data.lastClickTime = now();
		nextTick(() => {
			if (!swiper.destroyed) swiper.allowClick = true;
		});
		if (!data.isTouched || !data.isMoved || !swiper.swipeDirection || touches.diff === 0 && !data.loopSwapReset || data.currentTranslate === data.startTranslate && !data.loopSwapReset) {
			data.isTouched = false;
			data.isMoved = false;
			data.startMoving = false;
			return;
		}
		data.isTouched = false;
		data.isMoved = false;
		data.startMoving = false;
		let currentPos;
		if (params.followFinger) {
			currentPos = rtl ? swiper.translate : -swiper.translate;
		} else {
			currentPos = -data.currentTranslate;
		}
		if (params.cssMode) {
			return;
		}
		if (params.freeMode && params.freeMode.enabled) {
			swiper.freeMode.onTouchEnd({
				currentPos
			});
			return;
		}

		// Find current slide
		const swipeToLast = currentPos >= -swiper.maxTranslate() && !swiper.params.loop;
		let stopIndex = 0;
		let groupSize = swiper.slidesSizesGrid[0];
		for (let i = 0; i < slidesGrid.length; i += i < params.slidesPerGroupSkip ? 1 : params.slidesPerGroup) {
			const increment = i < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup;
			if (typeof slidesGrid[i + increment] !== 'undefined') {
				if (swipeToLast || currentPos >= slidesGrid[i] && currentPos < slidesGrid[i + increment]) {
					stopIndex = i;
					groupSize = slidesGrid[i + increment] - slidesGrid[i];
				}
			} else if (swipeToLast || currentPos >= slidesGrid[i]) {
				stopIndex = i;
				groupSize = slidesGrid[slidesGrid.length - 1] - slidesGrid[slidesGrid.length - 2];
			}
		}
		let rewindFirstIndex = null;
		let rewindLastIndex = null;
		if (params.rewind) {
			if (swiper.isBeginning) {
				rewindLastIndex = params.virtual && params.virtual.enabled && swiper.virtual ? swiper.virtual.slides.length - 1 : swiper.slides.length - 1;
			} else if (swiper.isEnd) {
				rewindFirstIndex = 0;
			}
		}
		// Find current slide size
		const ratio = (currentPos - slidesGrid[stopIndex]) / groupSize;
		const increment = stopIndex < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup;
		if (timeDiff > params.longSwipesMs) {
			// Long touches
			if (!params.longSwipes) {
				swiper.slideTo(swiper.activeIndex);
				return;
			}
			if (swiper.swipeDirection === 'next') {
				if (ratio >= params.longSwipesRatio) swiper.slideTo(params.rewind && swiper.isEnd ? rewindFirstIndex : stopIndex + increment);else swiper.slideTo(stopIndex);
			}
			if (swiper.swipeDirection === 'prev') {
				if (ratio > 1 - params.longSwipesRatio) {
					swiper.slideTo(stopIndex + increment);
				} else if (rewindLastIndex !== null && ratio < 0 && Math.abs(ratio) > params.longSwipesRatio) {
					swiper.slideTo(rewindLastIndex);
				} else {
					swiper.slideTo(stopIndex);
				}
			}
		} else {
			// Short swipes
			if (!params.shortSwipes) {
				swiper.slideTo(swiper.activeIndex);
				return;
			}
			const isNavButtonTarget = swiper.navigation && (e.target === swiper.navigation.nextEl || e.target === swiper.navigation.prevEl);
			if (!isNavButtonTarget) {
				if (swiper.swipeDirection === 'next') {
					swiper.slideTo(rewindFirstIndex !== null ? rewindFirstIndex : stopIndex + increment);
				}
				if (swiper.swipeDirection === 'prev') {
					swiper.slideTo(rewindLastIndex !== null ? rewindLastIndex : stopIndex);
				}
			} else if (e.target === swiper.navigation.nextEl) {
				swiper.slideTo(stopIndex + increment);
			} else {
				swiper.slideTo(stopIndex);
			}
		}
	}

	function onResize() {
		const swiper = this;
		const {
			params,
			el
		} = swiper;
		if (el && el.offsetWidth === 0) return;

		// Breakpoints
		if (params.breakpoints) {
			swiper.setBreakpoint();
		}

		// Save locks
		const {
			allowSlideNext,
			allowSlidePrev,
			snapGrid
		} = swiper;
		const isVirtual = swiper.virtual && swiper.params.virtual.enabled;

		// Disable locks on resize
		swiper.allowSlideNext = true;
		swiper.allowSlidePrev = true;
		swiper.updateSize();
		swiper.updateSlides();
		swiper.updateSlidesClasses();
		const isVirtualLoop = isVirtual && params.loop;
		if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !swiper.isBeginning && !swiper.params.centeredSlides && !isVirtualLoop) {
			const slides = isVirtual ? swiper.virtual.slides : swiper.slides;
			swiper.slideTo(slides.length - 1, 0, false, true);
		} else {
			if (swiper.params.loop && !isVirtual) {
				swiper.slideToLoop(swiper.realIndex, 0, false, true);
			} else {
				swiper.slideTo(swiper.activeIndex, 0, false, true);
			}
		}
		if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) {
			clearTimeout(swiper.autoplay.resizeTimeout);
			swiper.autoplay.resizeTimeout = setTimeout(() => {
				if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) {
					swiper.autoplay.resume();
				}
			}, 500);
		}
		// Return locks after resize
		swiper.allowSlidePrev = allowSlidePrev;
		swiper.allowSlideNext = allowSlideNext;
		if (swiper.params.watchOverflow && snapGrid !== swiper.snapGrid) {
			swiper.checkOverflow();
		}
	}

	function onClick(e) {
		const swiper = this;
		if (!swiper.enabled) return;
		if (!swiper.allowClick) {
			if (swiper.params.preventClicks) e.preventDefault();
			if (swiper.params.preventClicksPropagation && swiper.animating) {
				e.stopPropagation();
				e.stopImmediatePropagation();
			}
		}
	}

	function onScroll() {
		const swiper = this;
		const {
			wrapperEl,
			rtlTranslate,
			enabled
		} = swiper;
		if (!enabled) return;
		swiper.previousTranslate = swiper.translate;
		if (swiper.isHorizontal()) {
			swiper.translate = -wrapperEl.scrollLeft;
		} else {
			swiper.translate = -wrapperEl.scrollTop;
		}
		// eslint-disable-next-line
		if (swiper.translate === 0) swiper.translate = 0;
		swiper.updateActiveIndex();
		swiper.updateSlidesClasses();
		let newProgress;
		const translatesDiff = swiper.maxTranslate() - swiper.minTranslate();
		if (translatesDiff === 0) {
			newProgress = 0;
		} else {
			newProgress = (swiper.translate - swiper.minTranslate()) / translatesDiff;
		}
		if (newProgress !== swiper.progress) {
			swiper.updateProgress(rtlTranslate ? -swiper.translate : swiper.translate);
		}
		swiper.emit('setTranslate', swiper.translate, false);
	}

	function onLoad(e) {
		const swiper = this;
		processLazyPreloader(swiper, e.target);
		if (swiper.params.cssMode || swiper.params.slidesPerView !== 'auto' && !swiper.params.autoHeight) {
			return;
		}
		swiper.update();
	}

	function onDocumentTouchStart() {
		const swiper = this;
		if (swiper.documentTouchHandlerProceeded) return;
		swiper.documentTouchHandlerProceeded = true;
		if (swiper.params.touchReleaseOnEdges) {
			swiper.el.style.touchAction = 'auto';
		}
	}

	const events = (swiper, method) => {
		const document = getDocument();
		const {
			params,
			el,
			wrapperEl,
			device
		} = swiper;
		const capture = !!params.nested;
		const domMethod = method === 'on' ? 'addEventListener' : 'removeEventListener';
		const swiperMethod = method;
		if (!el || typeof el === 'string') return;

		// Touch Events
		document[domMethod]('touchstart', swiper.onDocumentTouchStart, {
			passive: false,
			capture
		});
		el[domMethod]('touchstart', swiper.onTouchStart, {
			passive: false
		});
		el[domMethod]('pointerdown', swiper.onTouchStart, {
			passive: false
		});
		document[domMethod]('touchmove', swiper.onTouchMove, {
			passive: false,
			capture
		});
		document[domMethod]('pointermove', swiper.onTouchMove, {
			passive: false,
			capture
		});
		document[domMethod]('touchend', swiper.onTouchEnd, {
			passive: true
		});
		document[domMethod]('pointerup', swiper.onTouchEnd, {
			passive: true
		});
		document[domMethod]('pointercancel', swiper.onTouchEnd, {
			passive: true
		});
		document[domMethod]('touchcancel', swiper.onTouchEnd, {
			passive: true
		});
		document[domMethod]('pointerout', swiper.onTouchEnd, {
			passive: true
		});
		document[domMethod]('pointerleave', swiper.onTouchEnd, {
			passive: true
		});
		document[domMethod]('contextmenu', swiper.onTouchEnd, {
			passive: true
		});

		// Prevent Links Clicks
		if (params.preventClicks || params.preventClicksPropagation) {
			el[domMethod]('click', swiper.onClick, true);
		}
		if (params.cssMode) {
			wrapperEl[domMethod]('scroll', swiper.onScroll);
		}

		// Resize handler
		if (params.updateOnWindowResize) {
			swiper[swiperMethod](device.ios || device.android ? 'resize orientationchange observerUpdate' : 'resize observerUpdate', onResize, true);
		} else {
			swiper[swiperMethod]('observerUpdate', onResize, true);
		}

		// Images loader
		el[domMethod]('load', swiper.onLoad, {
			capture: true
		});
	};
	function attachEvents() {
		const swiper = this;
		const {
			params
		} = swiper;
		swiper.onTouchStart = onTouchStart.bind(swiper);
		swiper.onTouchMove = onTouchMove.bind(swiper);
		swiper.onTouchEnd = onTouchEnd.bind(swiper);
		swiper.onDocumentTouchStart = onDocumentTouchStart.bind(swiper);
		if (params.cssMode) {
			swiper.onScroll = onScroll.bind(swiper);
		}
		swiper.onClick = onClick.bind(swiper);
		swiper.onLoad = onLoad.bind(swiper);
		events(swiper, 'on');
	}
	function detachEvents() {
		const swiper = this;
		events(swiper, 'off');
	}
	var events$1 = {
		attachEvents,
		detachEvents
	};

	const isGridEnabled = (swiper, params) => {
		return swiper.grid && params.grid && params.grid.rows > 1;
	};
	function setBreakpoint() {
		const swiper = this;
		const {
			realIndex,
			initialized,
			params,
			el
		} = swiper;
		const breakpoints = params.breakpoints;
		if (!breakpoints || breakpoints && Object.keys(breakpoints).length === 0) return;
		const document = getDocument();

		// Get breakpoint for window/container width and update parameters
		const breakpointsBase = params.breakpointsBase === 'window' || !params.breakpointsBase ? params.breakpointsBase : 'container';
		const breakpointContainer = ['window', 'container'].includes(params.breakpointsBase) || !params.breakpointsBase ? swiper.el : document.querySelector(params.breakpointsBase);
		const breakpoint = swiper.getBreakpoint(breakpoints, breakpointsBase, breakpointContainer);
		if (!breakpoint || swiper.currentBreakpoint === breakpoint) return;
		const breakpointOnlyParams = breakpoint in breakpoints ? breakpoints[breakpoint] : undefined;
		const breakpointParams = breakpointOnlyParams || swiper.originalParams;
		const wasMultiRow = isGridEnabled(swiper, params);
		const isMultiRow = isGridEnabled(swiper, breakpointParams);
		const wasGrabCursor = swiper.params.grabCursor;
		const isGrabCursor = breakpointParams.grabCursor;
		const wasEnabled = params.enabled;
		if (wasMultiRow && !isMultiRow) {
			el.classList.remove(`${params.containerModifierClass}grid`, `${params.containerModifierClass}grid-column`);
			swiper.emitContainerClasses();
		} else if (!wasMultiRow && isMultiRow) {
			el.classList.add(`${params.containerModifierClass}grid`);
			if (breakpointParams.grid.fill && breakpointParams.grid.fill === 'column' || !breakpointParams.grid.fill && params.grid.fill === 'column') {
				el.classList.add(`${params.containerModifierClass}grid-column`);
			}
			swiper.emitContainerClasses();
		}
		if (wasGrabCursor && !isGrabCursor) {
			swiper.unsetGrabCursor();
		} else if (!wasGrabCursor && isGrabCursor) {
			swiper.setGrabCursor();
		}

		// Toggle navigation, pagination, scrollbar
		['navigation', 'pagination', 'scrollbar'].forEach(prop => {
			if (typeof breakpointParams[prop] === 'undefined') return;
			const wasModuleEnabled = params[prop] && params[prop].enabled;
			const isModuleEnabled = breakpointParams[prop] && breakpointParams[prop].enabled;
			if (wasModuleEnabled && !isModuleEnabled) {
				swiper[prop].disable();
			}
			if (!wasModuleEnabled && isModuleEnabled) {
				swiper[prop].enable();
			}
		});
		const directionChanged = breakpointParams.direction && breakpointParams.direction !== params.direction;
		const needsReLoop = params.loop && (breakpointParams.slidesPerView !== params.slidesPerView || directionChanged);
		const wasLoop = params.loop;
		if (directionChanged && initialized) {
			swiper.changeDirection();
		}
		extend(swiper.params, breakpointParams);
		const isEnabled = swiper.params.enabled;
		const hasLoop = swiper.params.loop;
		Object.assign(swiper, {
			allowTouchMove: swiper.params.allowTouchMove,
			allowSlideNext: swiper.params.allowSlideNext,
			allowSlidePrev: swiper.params.allowSlidePrev
		});
		if (wasEnabled && !isEnabled) {
			swiper.disable();
		} else if (!wasEnabled && isEnabled) {
			swiper.enable();
		}
		swiper.currentBreakpoint = breakpoint;
		swiper.emit('_beforeBreakpoint', breakpointParams);
		if (initialized) {
			if (needsReLoop) {
				swiper.loopDestroy();
				swiper.loopCreate(realIndex);
				swiper.updateSlides();
			} else if (!wasLoop && hasLoop) {
				swiper.loopCreate(realIndex);
				swiper.updateSlides();
			} else if (wasLoop && !hasLoop) {
				swiper.loopDestroy();
			}
		}
		swiper.emit('breakpoint', breakpointParams);
	}

	function getBreakpoint(breakpoints, base = 'window', containerEl) {
		if (!breakpoints || base === 'container' && !containerEl) return undefined;
		let breakpoint = false;
		const window = getWindow();
		const currentHeight = base === 'window' ? window.innerHeight : containerEl.clientHeight;
		const points = Object.keys(breakpoints).map(point => {
			if (typeof point === 'string' && point.indexOf('@') === 0) {
				const minRatio = parseFloat(point.substr(1));
				const value = currentHeight * minRatio;
				return {
					value,
					point
				};
			}
			return {
				value: point,
				point
			};
		});
		points.sort((a, b) => parseInt(a.value, 10) - parseInt(b.value, 10));
		for (let i = 0; i < points.length; i += 1) {
			const {
				point,
				value
			} = points[i];
			if (base === 'window') {
				if (window.matchMedia(`(min-width: ${value}px)`).matches) {
					breakpoint = point;
				}
			} else if (value <= containerEl.clientWidth) {
				breakpoint = point;
			}
		}
		return breakpoint || 'max';
	}

	var breakpoints = {
		setBreakpoint,
		getBreakpoint
	};

	function prepareClasses(entries, prefix) {
		const resultClasses = [];
		entries.forEach(item => {
			if (typeof item === 'object') {
				Object.keys(item).forEach(classNames => {
					if (item[classNames]) {
						resultClasses.push(prefix + classNames);
					}
				});
			} else if (typeof item === 'string') {
				resultClasses.push(prefix + item);
			}
		});
		return resultClasses;
	}
	function addClasses() {
		const swiper = this;
		const {
			classNames,
			params,
			rtl,
			el,
			device
		} = swiper;
		// prettier-ignore
		const suffixes = prepareClasses(['initialized', params.direction, {
			'free-mode': swiper.params.freeMode && params.freeMode.enabled
		}, {
			'autoheight': params.autoHeight
		}, {
			'rtl': rtl
		}, {
			'grid': params.grid && params.grid.rows > 1
		}, {
			'grid-column': params.grid && params.grid.rows > 1 && params.grid.fill === 'column'
		}, {
			'android': device.android
		}, {
			'ios': device.ios
		}, {
			'css-mode': params.cssMode
		}, {
			'centered': params.cssMode && params.centeredSlides
		}, {
			'watch-progress': params.watchSlidesProgress
		}], params.containerModifierClass);
		classNames.push(...suffixes);
		el.classList.add(...classNames);
		swiper.emitContainerClasses();
	}

	function removeClasses() {
		const swiper = this;
		const {
			el,
			classNames
		} = swiper;
		if (!el || typeof el === 'string') return;
		el.classList.remove(...classNames);
		swiper.emitContainerClasses();
	}

	var classes = {
		addClasses,
		removeClasses
	};

	function checkOverflow() {
		const swiper = this;
		const {
			isLocked: wasLocked,
			params
		} = swiper;
		const {
			slidesOffsetBefore
		} = params;
		if (slidesOffsetBefore) {
			const lastSlideIndex = swiper.slides.length - 1;
			const lastSlideRightEdge = swiper.slidesGrid[lastSlideIndex] + swiper.slidesSizesGrid[lastSlideIndex] + slidesOffsetBefore * 2;
			swiper.isLocked = swiper.size > lastSlideRightEdge;
		} else {
			swiper.isLocked = swiper.snapGrid.length === 1;
		}
		if (params.allowSlideNext === true) {
			swiper.allowSlideNext = !swiper.isLocked;
		}
		if (params.allowSlidePrev === true) {
			swiper.allowSlidePrev = !swiper.isLocked;
		}
		if (wasLocked && wasLocked !== swiper.isLocked) {
			swiper.isEnd = false;
		}
		if (wasLocked !== swiper.isLocked) {
			swiper.emit(swiper.isLocked ? 'lock' : 'unlock');
		}
	}
	var checkOverflow$1 = {
		checkOverflow
	};

	var defaults = {
		init: true,
		direction: 'horizontal',
		oneWayMovement: false,
		swiperElementNodeName: 'SWIPER-CONTAINER',
		touchEventsTarget: 'wrapper',
		initialSlide: 0,
		speed: 300,
		cssMode: false,
		updateOnWindowResize: true,
		resizeObserver: true,
		nested: false,
		createElements: false,
		eventsPrefix: 'swiper',
		enabled: true,
		focusableElements: 'input, select, option, textarea, button, video, label',
		// Overrides
		width: null,
		height: null,
		//
		preventInteractionOnTransition: false,
		// ssr
		userAgent: null,
		url: null,
		// To support iOS's swipe-to-go-back gesture (when being used in-app).
		edgeSwipeDetection: false,
		edgeSwipeThreshold: 20,
		// Autoheight
		autoHeight: false,
		// Set wrapper width
		setWrapperSize: false,
		// Virtual Translate
		virtualTranslate: false,
		// Effects
		effect: 'slide',
		// 'slide' or 'fade' or 'cube' or 'coverflow' or 'flip'

		// Breakpoints
		breakpoints: undefined,
		breakpointsBase: 'window',
		// Slides grid
		spaceBetween: 0,
		slidesPerView: 1,
		slidesPerGroup: 1,
		slidesPerGroupSkip: 0,
		slidesPerGroupAuto: false,
		centeredSlides: false,
		centeredSlidesBounds: false,
		slidesOffsetBefore: 0,
		// in px
		slidesOffsetAfter: 0,
		// in px
		normalizeSlideIndex: true,
		centerInsufficientSlides: false,
		snapToSlideEdge: false,
		// Disable swiper and hide navigation when container not overflow
		watchOverflow: true,
		// Round length
		roundLengths: false,
		// Touches
		touchRatio: 1,
		touchAngle: 45,
		simulateTouch: true,
		shortSwipes: true,
		longSwipes: true,
		longSwipesRatio: 0.5,
		longSwipesMs: 300,
		followFinger: true,
		allowTouchMove: true,
		threshold: 5,
		touchMoveStopPropagation: false,
		touchStartPreventDefault: true,
		touchStartForcePreventDefault: false,
		touchReleaseOnEdges: false,
		// Unique Navigation Elements
		uniqueNavElements: true,
		// Resistance
		resistance: true,
		resistanceRatio: 0.85,
		// Progress
		watchSlidesProgress: false,
		// Cursor
		grabCursor: false,
		// Clicks
		preventClicks: true,
		preventClicksPropagation: true,
		slideToClickedSlide: false,
		// loop
		loop: false,
		loopAddBlankSlides: true,
		loopAdditionalSlides: 0,
		loopPreventsSliding: true,
		// rewind
		rewind: false,
		// Swiping/no swiping
		allowSlidePrev: true,
		allowSlideNext: true,
		swipeHandler: null,
		// '.swipe-handler',
		noSwiping: true,
		noSwipingClass: 'swiper-no-swiping',
		noSwipingSelector: null,
		// Passive Listeners
		passiveListeners: true,
		maxBackfaceHiddenSlides: 10,
		// NS
		containerModifierClass: 'swiper-',
		// NEW
		slideClass: 'swiper-slide',
		slideBlankClass: 'swiper-slide-blank',
		slideActiveClass: 'swiper-slide-active',
		slideVisibleClass: 'swiper-slide-visible',
		slideFullyVisibleClass: 'swiper-slide-fully-visible',
		slideNextClass: 'swiper-slide-next',
		slidePrevClass: 'swiper-slide-prev',
		wrapperClass: 'swiper-wrapper',
		lazyPreloaderClass: 'swiper-lazy-preloader',
		lazyPreloadPrevNext: 0,
		// Callbacks
		runCallbacksOnInit: true,
		// Internals
		_emitClasses: false
	};

	function moduleExtendParams(params, allModulesParams) {
		return function extendParams(obj = {}) {
			const moduleParamName = Object.keys(obj)[0];
			const moduleParams = obj[moduleParamName];
			if (typeof moduleParams !== 'object' || moduleParams === null) {
				extend(allModulesParams, obj);
				return;
			}
			if (params[moduleParamName] === true) {
				params[moduleParamName] = {
					enabled: true
				};
			}
			if (moduleParamName === 'navigation' && params[moduleParamName] && params[moduleParamName].enabled && !params[moduleParamName].prevEl && !params[moduleParamName].nextEl) {
				params[moduleParamName].auto = true;
			}
			if (['pagination', 'scrollbar'].indexOf(moduleParamName) >= 0 && params[moduleParamName] && params[moduleParamName].enabled && !params[moduleParamName].el) {
				params[moduleParamName].auto = true;
			}
			if (!(moduleParamName in params && 'enabled' in moduleParams)) {
				extend(allModulesParams, obj);
				return;
			}
			if (typeof params[moduleParamName] === 'object' && !('enabled' in params[moduleParamName])) {
				params[moduleParamName].enabled = true;
			}
			if (!params[moduleParamName]) params[moduleParamName] = {
				enabled: false
			};
			extend(allModulesParams, obj);
		};
	}

	/* eslint no-param-reassign: "off" */
	const prototypes = {
		eventsEmitter,
		update,
		translate,
		transition,
		slide,
		loop,
		grabCursor,
		events: events$1,
		breakpoints,
		checkOverflow: checkOverflow$1,
		classes
	};
	const extendedDefaults = {};
	class Swiper {
		constructor(...args) {
			let el;
			let params;
			if (args.length === 1 && args[0].constructor && Object.prototype.toString.call(args[0]).slice(8, -1) === 'Object') {
				params = args[0];
			} else {
				[el, params] = args;
			}
			if (!params) params = {};
			params = extend({}, params);
			if (el && !params.el) params.el = el;
			const document = getDocument();
			if (params.el && typeof params.el === 'string' && document.querySelectorAll(params.el).length > 1) {
				const swipers = [];
				document.querySelectorAll(params.el).forEach(containerEl => {
					const newParams = extend({}, params, {
						el: containerEl
					});
					swipers.push(new Swiper(newParams));
				});
				// eslint-disable-next-line no-constructor-return
				return swipers;
			}

			// Swiper Instance
			const swiper = this;
			swiper.__swiper__ = true;
			swiper.support = getSupport();
			swiper.device = getDevice({
				userAgent: params.userAgent
			});
			swiper.browser = getBrowser();
			swiper.eventsListeners = {};
			swiper.eventsAnyListeners = [];
			swiper.modules = [...swiper.__modules__];
			if (params.modules && Array.isArray(params.modules)) {
				params.modules.forEach(mod => {
					if (typeof mod === 'function' && swiper.modules.indexOf(mod) < 0) {
						swiper.modules.push(mod);
					}
				});
			}
			const allModulesParams = {};
			swiper.modules.forEach(mod => {
				mod({
					params,
					swiper,
					extendParams: moduleExtendParams(params, allModulesParams),
					on: swiper.on.bind(swiper),
					once: swiper.once.bind(swiper),
					off: swiper.off.bind(swiper),
					emit: swiper.emit.bind(swiper)
				});
			});

			// Extend defaults with modules params
			const swiperParams = extend({}, defaults, allModulesParams);

			// Extend defaults with passed params
			swiper.params = extend({}, swiperParams, extendedDefaults, params);
			swiper.originalParams = extend({}, swiper.params);
			swiper.passedParams = extend({}, params);

			// add event listeners
			if (swiper.params && swiper.params.on) {
				Object.keys(swiper.params.on).forEach(eventName => {
					swiper.on(eventName, swiper.params.on[eventName]);
				});
			}
			if (swiper.params && swiper.params.onAny) {
				swiper.onAny(swiper.params.onAny);
			}

			// Extend Swiper
			Object.assign(swiper, {
				enabled: swiper.params.enabled,
				el,
				// Classes
				classNames: [],
				// Slides
				slides: [],
				slidesGrid: [],
				snapGrid: [],
				slidesSizesGrid: [],
				// isDirection
				isHorizontal() {
					return swiper.params.direction === 'horizontal';
				},
				isVertical() {
					return swiper.params.direction === 'vertical';
				},
				// Indexes
				activeIndex: 0,
				realIndex: 0,
				//
				isBeginning: true,
				isEnd: false,
				// Props
				translate: 0,
				previousTranslate: 0,
				progress: 0,
				velocity: 0,
				animating: false,
				cssOverflowAdjustment() {
					// Returns 0 unless `translate` is > 2**23
					// Should be subtracted from css values to prevent overflow
					return Math.trunc(this.translate / 2 ** 23) * 2 ** 23;
				},
				// Locks
				allowSlideNext: swiper.params.allowSlideNext,
				allowSlidePrev: swiper.params.allowSlidePrev,
				// Touch Events
				touchEventsData: {
					isTouched: undefined,
					isMoved: undefined,
					allowTouchCallbacks: undefined,
					touchStartTime: undefined,
					isScrolling: undefined,
					currentTranslate: undefined,
					startTranslate: undefined,
					allowThresholdMove: undefined,
					// Form elements to match
					focusableElements: swiper.params.focusableElements,
					// Last click time
					lastClickTime: 0,
					clickTimeout: undefined,
					// Velocities
					velocities: [],
					allowMomentumBounce: undefined,
					startMoving: undefined,
					pointerId: null,
					touchId: null
				},
				// Clicks
				allowClick: true,
				// Touches
				allowTouchMove: swiper.params.allowTouchMove,
				touches: {
					startX: 0,
					startY: 0,
					currentX: 0,
					currentY: 0,
					diff: 0
				},
				// Images
				imagesToLoad: [],
				imagesLoaded: 0
			});
			swiper.emit('_swiper');

			// Init
			if (swiper.params.init) {
				swiper.init();
			}

			// Return app instance
			// eslint-disable-next-line no-constructor-return
			return swiper;
		}
		getDirectionLabel(property) {
			if (this.isHorizontal()) {
				return property;
			}
			// prettier-ignore
			return {
				'width': 'height',
				'margin-top': 'margin-left',
				'margin-bottom ': 'margin-right',
				'margin-left': 'margin-top',
				'margin-right': 'margin-bottom',
				'padding-left': 'padding-top',
				'padding-right': 'padding-bottom',
				'marginRight': 'marginBottom'
			}[property];
		}
		getSlideIndex(slideEl) {
			const {
				slidesEl,
				params
			} = this;
			const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
			const firstSlideIndex = elementIndex(slides[0]);
			return elementIndex(slideEl) - firstSlideIndex;
		}
		getSlideIndexByData(index) {
			return this.getSlideIndex(this.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === index));
		}
		getSlideIndexWhenGrid(index) {
			if (this.grid && this.params.grid && this.params.grid.rows > 1) {
				if (this.params.grid.fill === 'column') {
					index = Math.floor(index / this.params.grid.rows);
				} else if (this.params.grid.fill === 'row') {
					index = index % Math.ceil(this.slides.length / this.params.grid.rows);
				}
			}
			return index;
		}
		recalcSlides() {
			const swiper = this;
			const {
				slidesEl,
				params
			} = swiper;
			swiper.slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
		}
		enable() {
			const swiper = this;
			if (swiper.enabled) return;
			swiper.enabled = true;
			if (swiper.params.grabCursor) {
				swiper.setGrabCursor();
			}
			swiper.emit('enable');
		}
		disable() {
			const swiper = this;
			if (!swiper.enabled) return;
			swiper.enabled = false;
			if (swiper.params.grabCursor) {
				swiper.unsetGrabCursor();
			}
			swiper.emit('disable');
		}
		setProgress(progress, speed) {
			const swiper = this;
			progress = Math.min(Math.max(progress, 0), 1);
			const min = swiper.minTranslate();
			const max = swiper.maxTranslate();
			const current = (max - min) * progress + min;
			swiper.translateTo(current, typeof speed === 'undefined' ? 0 : speed);
			swiper.updateActiveIndex();
			swiper.updateSlidesClasses();
		}
		emitContainerClasses() {
			const swiper = this;
			if (!swiper.params._emitClasses || !swiper.el) return;
			const cls = swiper.el.className.split(' ').filter(className => {
				return className.indexOf('swiper') === 0 || className.indexOf(swiper.params.containerModifierClass) === 0;
			});
			swiper.emit('_containerClasses', cls.join(' '));
		}
		getSlideClasses(slideEl) {
			const swiper = this;
			if (swiper.destroyed) return '';
			return slideEl.className.split(' ').filter(className => {
				return className.indexOf('swiper-slide') === 0 || className.indexOf(swiper.params.slideClass) === 0;
			}).join(' ');
		}
		emitSlidesClasses() {
			const swiper = this;
			if (!swiper.params._emitClasses || !swiper.el) return;
			const updates = [];
			swiper.slides.forEach(slideEl => {
				const classNames = swiper.getSlideClasses(slideEl);
				updates.push({
					slideEl,
					classNames
				});
				swiper.emit('_slideClass', slideEl, classNames);
			});
			swiper.emit('_slideClasses', updates);
		}
		slidesPerViewDynamic(view = 'current', exact = false) {
			const swiper = this;
			const {
				params,
				slides,
				slidesGrid,
				slidesSizesGrid,
				size: swiperSize,
				activeIndex
			} = swiper;
			let spv = 1;
			if (typeof params.slidesPerView === 'number') return params.slidesPerView;
			if (params.centeredSlides) {
				let slideSize = slides[activeIndex] ? Math.ceil(slides[activeIndex].swiperSlideSize) : 0;
				let breakLoop;
				for (let i = activeIndex + 1; i < slides.length; i += 1) {
					if (slides[i] && !breakLoop) {
						slideSize += Math.ceil(slides[i].swiperSlideSize);
						spv += 1;
						if (slideSize > swiperSize) breakLoop = true;
					}
				}
				for (let i = activeIndex - 1; i >= 0; i -= 1) {
					if (slides[i] && !breakLoop) {
						slideSize += slides[i].swiperSlideSize;
						spv += 1;
						if (slideSize > swiperSize) breakLoop = true;
					}
				}
			} else {
				// eslint-disable-next-line
				if (view === 'current') {
					for (let i = activeIndex + 1; i < slides.length; i += 1) {
						const slideInView = exact ? slidesGrid[i] + slidesSizesGrid[i] - slidesGrid[activeIndex] < swiperSize : slidesGrid[i] - slidesGrid[activeIndex] < swiperSize;
						if (slideInView) {
							spv += 1;
						}
					}
				} else {
					// previous
					for (let i = activeIndex - 1; i >= 0; i -= 1) {
						const slideInView = slidesGrid[activeIndex] - slidesGrid[i] < swiperSize;
						if (slideInView) {
							spv += 1;
						}
					}
				}
			}
			return spv;
		}
		update() {
			const swiper = this;
			if (!swiper || swiper.destroyed) return;
			const {
				snapGrid,
				params
			} = swiper;
			// Breakpoints
			if (params.breakpoints) {
				swiper.setBreakpoint();
			}
			[...swiper.el.querySelectorAll('[loading="lazy"]')].forEach(imageEl => {
				if (imageEl.complete) {
					processLazyPreloader(swiper, imageEl);
				}
			});
			swiper.updateSize();
			swiper.updateSlides();
			swiper.updateProgress();
			swiper.updateSlidesClasses();
			function setTranslate() {
				const translateValue = swiper.rtlTranslate ? swiper.translate * -1 : swiper.translate;
				const newTranslate = Math.min(Math.max(translateValue, swiper.maxTranslate()), swiper.minTranslate());
				swiper.setTranslate(newTranslate);
				swiper.updateActiveIndex();
				swiper.updateSlidesClasses();
			}
			let translated;
			if (params.freeMode && params.freeMode.enabled && !params.cssMode) {
				setTranslate();
				if (params.autoHeight) {
					swiper.updateAutoHeight();
				}
			} else {
				if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !params.centeredSlides) {
					const slides = swiper.virtual && params.virtual.enabled ? swiper.virtual.slides : swiper.slides;
					translated = swiper.slideTo(slides.length - 1, 0, false, true);
				} else {
					translated = swiper.slideTo(swiper.activeIndex, 0, false, true);
				}
				if (!translated) {
					setTranslate();
				}
			}
			if (params.watchOverflow && snapGrid !== swiper.snapGrid) {
				swiper.checkOverflow();
			}
			swiper.emit('update');
		}
		changeDirection(newDirection, needUpdate = true) {
			const swiper = this;
			const currentDirection = swiper.params.direction;
			if (!newDirection) {
				// eslint-disable-next-line
				newDirection = currentDirection === 'horizontal' ? 'vertical' : 'horizontal';
			}
			if (newDirection === currentDirection || newDirection !== 'horizontal' && newDirection !== 'vertical') {
				return swiper;
			}
			swiper.el.classList.remove(`${swiper.params.containerModifierClass}${currentDirection}`);
			swiper.el.classList.add(`${swiper.params.containerModifierClass}${newDirection}`);
			swiper.emitContainerClasses();
			swiper.params.direction = newDirection;
			swiper.slides.forEach(slideEl => {
				if (newDirection === 'vertical') {
					slideEl.style.width = '';
				} else {
					slideEl.style.height = '';
				}
			});
			swiper.emit('changeDirection');
			if (needUpdate) swiper.update();
			return swiper;
		}
		changeLanguageDirection(direction) {
			const swiper = this;
			if (swiper.rtl && direction === 'rtl' || !swiper.rtl && direction === 'ltr') return;
			swiper.rtl = direction === 'rtl';
			swiper.rtlTranslate = swiper.params.direction === 'horizontal' && swiper.rtl;
			if (swiper.rtl) {
				swiper.el.classList.add(`${swiper.params.containerModifierClass}rtl`);
				swiper.el.dir = 'rtl';
			} else {
				swiper.el.classList.remove(`${swiper.params.containerModifierClass}rtl`);
				swiper.el.dir = 'ltr';
			}
			swiper.update();
		}
		mount(element) {
			const swiper = this;
			if (swiper.mounted) return true;

			// Find el
			let el = element || swiper.params.el;
			if (typeof el === 'string') {
				el = document.querySelector(el);
			}
			if (!el) {
				return false;
			}
			el.swiper = swiper;
			if (el.parentNode && el.parentNode.host && el.parentNode.host.nodeName === swiper.params.swiperElementNodeName.toUpperCase()) {
				swiper.isElement = true;
			}
			const getWrapperSelector = () => {
				return `.${(swiper.params.wrapperClass || '').trim().split(' ').join('.')}`;
			};
			const getWrapper = () => {
				if (el && el.shadowRoot && el.shadowRoot.querySelector) {
					const res = el.shadowRoot.querySelector(getWrapperSelector());
					// Children needs to return slot items
					return res;
				}
				return elementChildren(el, getWrapperSelector())[0];
			};
			// Find Wrapper
			let wrapperEl = getWrapper();
			if (!wrapperEl && swiper.params.createElements) {
				wrapperEl = createElement('div', swiper.params.wrapperClass);
				el.append(wrapperEl);
				elementChildren(el, `.${swiper.params.slideClass}`).forEach(slideEl => {
					wrapperEl.append(slideEl);
				});
			}
			Object.assign(swiper, {
				el,
				wrapperEl,
				slidesEl: swiper.isElement && !el.parentNode.host.slideSlots ? el.parentNode.host : wrapperEl,
				hostEl: swiper.isElement ? el.parentNode.host : el,
				mounted: true,
				// RTL
				rtl: el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl',
				rtlTranslate: swiper.params.direction === 'horizontal' && (el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl'),
				wrongRTL: elementStyle(wrapperEl, 'display') === '-webkit-box'
			});
			return true;
		}
		init(el) {
			const swiper = this;
			if (swiper.initialized) return swiper;
			const mounted = swiper.mount(el);
			if (mounted === false) return swiper;
			swiper.emit('beforeInit');

			// Set breakpoint
			if (swiper.params.breakpoints) {
				swiper.setBreakpoint();
			}

			// Add Classes
			swiper.addClasses();

			// Update size
			swiper.updateSize();

			// Update slides
			swiper.updateSlides();
			if (swiper.params.watchOverflow) {
				swiper.checkOverflow();
			}

			// Set Grab Cursor
			if (swiper.params.grabCursor && swiper.enabled) {
				swiper.setGrabCursor();
			}

			// Slide To Initial Slide
			if (swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) {
				swiper.slideTo(swiper.params.initialSlide + swiper.virtual.slidesBefore, 0, swiper.params.runCallbacksOnInit, false, true);
			} else {
				swiper.slideTo(swiper.params.initialSlide, 0, swiper.params.runCallbacksOnInit, false, true);
			}

			// Create loop
			if (swiper.params.loop) {
				swiper.loopCreate(undefined, true);
			}

			// Attach events
			swiper.attachEvents();
			const lazyElements = [...swiper.el.querySelectorAll('[loading="lazy"]')];
			if (swiper.isElement) {
				lazyElements.push(...swiper.hostEl.querySelectorAll('[loading="lazy"]'));
			}
			lazyElements.forEach(imageEl => {
				if (imageEl.complete) {
					processLazyPreloader(swiper, imageEl);
				} else {
					imageEl.addEventListener('load', e => {
						processLazyPreloader(swiper, e.target);
					});
				}
			});
			preload(swiper);

			// Init Flag
			swiper.initialized = true;
			preload(swiper);

			// Emit
			swiper.emit('init');
			swiper.emit('afterInit');
			return swiper;
		}
		destroy(deleteInstance = true, cleanStyles = true) {
			const swiper = this;
			const {
				params,
				el,
				wrapperEl,
				slides
			} = swiper;
			if (typeof swiper.params === 'undefined' || swiper.destroyed) {
				return null;
			}
			swiper.emit('beforeDestroy');

			// Init Flag
			swiper.initialized = false;

			// Detach events
			swiper.detachEvents();

			// Destroy loop
			if (params.loop) {
				swiper.loopDestroy();
			}

			// Cleanup styles
			if (cleanStyles) {
				swiper.removeClasses();
				if (el && typeof el !== 'string') {
					el.removeAttribute('style');
				}
				if (wrapperEl) {
					wrapperEl.removeAttribute('style');
				}
				if (slides && slides.length) {
					slides.forEach(slideEl => {
						slideEl.classList.remove(params.slideVisibleClass, params.slideFullyVisibleClass, params.slideActiveClass, params.slideNextClass, params.slidePrevClass);
						slideEl.removeAttribute('style');
						slideEl.removeAttribute('data-swiper-slide-index');
					});
				}
			}
			swiper.emit('destroy');

			// Detach emitter events
			Object.keys(swiper.eventsListeners).forEach(eventName => {
				swiper.off(eventName);
			});
			if (deleteInstance !== false) {
				if (swiper.el && typeof swiper.el !== 'string') {
					swiper.el.swiper = null;
				}
				deleteProps(swiper);
			}
			swiper.destroyed = true;
			return null;
		}
		static extendDefaults(newDefaults) {
			extend(extendedDefaults, newDefaults);
		}
		static get extendedDefaults() {
			return extendedDefaults;
		}
		static get defaults() {
			return defaults;
		}
		static installModule(mod) {
			if (!Swiper.prototype.__modules__) Swiper.prototype.__modules__ = [];
			const modules = Swiper.prototype.__modules__;
			if (typeof mod === 'function' && modules.indexOf(mod) < 0) {
				modules.push(mod);
			}
		}
		static use(module) {
			if (Array.isArray(module)) {
				module.forEach(m => Swiper.installModule(m));
				return Swiper;
			}
			Swiper.installModule(module);
			return Swiper;
		}
	}
	Object.keys(prototypes).forEach(prototypeGroup => {
		Object.keys(prototypes[prototypeGroup]).forEach(protoMethod => {
			Swiper.prototype[protoMethod] = prototypes[prototypeGroup][protoMethod];
		});
	});
	Swiper.use([Resize, Observer]);

	function createElementIfNotDefined(swiper, originalParams, params, checkProps) {
		if (swiper.params.createElements) {
			Object.keys(checkProps).forEach(key => {
				if (!params[key] && params.auto === true) {
					let element = elementChildren(swiper.el, `.${checkProps[key]}`)[0];
					if (!element) {
						element = createElement('div', checkProps[key]);
						element.className = checkProps[key];
						swiper.el.append(element);
					}
					params[key] = element;
					originalParams[key] = element;
				}
			});
		}
		return params;
	}

	const arrowSvg = `<svg class="swiper-navigation-icon" width="11" height="20" viewBox="0 0 11 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.38296 20.0762C0.111788 19.805 0.111788 19.3654 0.38296 19.0942L9.19758 10.2796L0.38296 1.46497C0.111788 1.19379 0.111788 0.754138 0.38296 0.482966C0.654131 0.211794 1.09379 0.211794 1.36496 0.482966L10.4341 9.55214C10.8359 9.9539 10.8359 10.6053 10.4341 11.007L1.36496 20.0762C1.09379 20.3474 0.654131 20.3474 0.38296 20.0762Z" fill="currentColor"/></svg>`;
	function Navigation({
							swiper,
							extendParams,
							on,
							emit
						}) {
		extendParams({
			navigation: {
				nextEl: null,
				prevEl: null,
				addIcons: true,
				hideOnClick: false,
				disabledClass: 'swiper-button-disabled',
				hiddenClass: 'swiper-button-hidden',
				lockClass: 'swiper-button-lock',
				navigationDisabledClass: 'swiper-navigation-disabled'
			}
		});
		swiper.navigation = {
			nextEl: null,
			prevEl: null,
			arrowSvg
		};
		function getEl(el) {
			let res;
			if (el && typeof el === 'string' && swiper.isElement) {
				res = swiper.el.querySelector(el) || swiper.hostEl.querySelector(el);
				if (res) return res;
			}
			if (el) {
				if (typeof el === 'string') res = [...document.querySelectorAll(el)];
				if (swiper.params.uniqueNavElements && typeof el === 'string' && res && res.length > 1 && swiper.el.querySelectorAll(el).length === 1) {
					res = swiper.el.querySelector(el);
				} else if (res && res.length === 1) {
					res = res[0];
				}
			}
			if (el && !res) return el;
			// if (Array.isArray(res) && res.length === 1) res = res[0];
			return res;
		}
		function toggleEl(el, disabled) {
			const params = swiper.params.navigation;
			el = makeElementsArray(el);
			el.forEach(subEl => {
				if (subEl) {
					subEl.classList[disabled ? 'add' : 'remove'](...params.disabledClass.split(' '));
					if (subEl.tagName === 'BUTTON') subEl.disabled = disabled;
					if (swiper.params.watchOverflow && swiper.enabled) {
						subEl.classList[swiper.isLocked ? 'add' : 'remove'](params.lockClass);
					}
				}
			});
		}
		function update() {
			// Update Navigation Buttons
			const {
				nextEl,
				prevEl
			} = swiper.navigation;
			if (swiper.params.loop) {
				toggleEl(prevEl, false);
				toggleEl(nextEl, false);
				return;
			}
			toggleEl(prevEl, swiper.isBeginning && !swiper.params.rewind);
			toggleEl(nextEl, swiper.isEnd && !swiper.params.rewind);
		}
		function onPrevClick(e) {
			e.preventDefault();
			if (swiper.isBeginning && !swiper.params.loop && !swiper.params.rewind) return;
			swiper.slidePrev();
			emit('navigationPrev');
		}
		function onNextClick(e) {
			e.preventDefault();
			if (swiper.isEnd && !swiper.params.loop && !swiper.params.rewind) return;
			swiper.slideNext();
			emit('navigationNext');
		}
		function init() {
			const params = swiper.params.navigation;
			swiper.params.navigation = createElementIfNotDefined(swiper, swiper.originalParams.navigation, swiper.params.navigation, {
				nextEl: 'swiper-button-next',
				prevEl: 'swiper-button-prev'
			});
			if (!(params.nextEl || params.prevEl)) return;
			let nextEl = getEl(params.nextEl);
			let prevEl = getEl(params.prevEl);
			Object.assign(swiper.navigation, {
				nextEl,
				prevEl
			});
			nextEl = makeElementsArray(nextEl);
			prevEl = makeElementsArray(prevEl);
			const initButton = (el, dir) => {
				if (el) {
					if (params.addIcons && el.matches('.swiper-button-next,.swiper-button-prev') && !el.querySelector('svg')) {
						const tempEl = document.createElement('div');
						setInnerHTML(tempEl, arrowSvg);
						el.appendChild(tempEl.querySelector('svg'));
						tempEl.remove();
					}
					el.addEventListener('click', dir === 'next' ? onNextClick : onPrevClick);
				}
				if (!swiper.enabled && el) {
					el.classList.add(...params.lockClass.split(' '));
				}
			};
			nextEl.forEach(el => initButton(el, 'next'));
			prevEl.forEach(el => initButton(el, 'prev'));
		}
		function destroy() {
			let {
				nextEl,
				prevEl
			} = swiper.navigation;
			nextEl = makeElementsArray(nextEl);
			prevEl = makeElementsArray(prevEl);
			const destroyButton = (el, dir) => {
				el.removeEventListener('click', dir === 'next' ? onNextClick : onPrevClick);
				el.classList.remove(...swiper.params.navigation.disabledClass.split(' '));
			};
			nextEl.forEach(el => destroyButton(el, 'next'));
			prevEl.forEach(el => destroyButton(el, 'prev'));
		}
		on('init', () => {
			if (swiper.params.navigation.enabled === false) {
				// eslint-disable-next-line
				disable();
			} else {
				init();
				update();
			}
		});
		on('toEdge fromEdge lock unlock', () => {
			update();
		});
		on('destroy', () => {
			destroy();
		});
		on('enable disable', () => {
			let {
				nextEl,
				prevEl
			} = swiper.navigation;
			nextEl = makeElementsArray(nextEl);
			prevEl = makeElementsArray(prevEl);
			if (swiper.enabled) {
				update();
				return;
			}
			[...nextEl, ...prevEl].filter(el => !!el).forEach(el => el.classList.add(swiper.params.navigation.lockClass));
		});
		on('click', (_s, e) => {
			let {
				nextEl,
				prevEl
			} = swiper.navigation;
			nextEl = makeElementsArray(nextEl);
			prevEl = makeElementsArray(prevEl);
			const targetEl = e.target;
			let targetIsButton = prevEl.includes(targetEl) || nextEl.includes(targetEl);
			if (swiper.isElement && !targetIsButton) {
				const path = e.path || e.composedPath && e.composedPath();
				if (path) {
					targetIsButton = path.find(pathEl => nextEl.includes(pathEl) || prevEl.includes(pathEl));
				}
			}
			if (swiper.params.navigation.hideOnClick && !targetIsButton) {
				if (swiper.pagination && swiper.params.pagination && swiper.params.pagination.clickable && (swiper.pagination.el === targetEl || swiper.pagination.el.contains(targetEl))) return;
				let isHidden;
				if (nextEl.length) {
					isHidden = nextEl[0].classList.contains(swiper.params.navigation.hiddenClass);
				} else if (prevEl.length) {
					isHidden = prevEl[0].classList.contains(swiper.params.navigation.hiddenClass);
				}
				if (isHidden === true) {
					emit('navigationShow');
				} else {
					emit('navigationHide');
				}
				[...nextEl, ...prevEl].filter(el => !!el).forEach(el => el.classList.toggle(swiper.params.navigation.hiddenClass));
			}
		});
		const enable = () => {
			swiper.el.classList.remove(...swiper.params.navigation.navigationDisabledClass.split(' '));
			init();
			update();
		};
		const disable = () => {
			swiper.el.classList.add(...swiper.params.navigation.navigationDisabledClass.split(' '));
			destroy();
		};
		Object.assign(swiper.navigation, {
			enable,
			disable,
			update,
			init,
			destroy
		});
	}

	function classesToSelector(classes = '') {
		// Escape all CSS selector special characters
		return `.${classes.trim().replace(/([\.:!+\/()[\]#>~*^$|=,'"@{}\\])/g, '\\$1') // eslint-disable-line
			.replace(/ /g, '.')}`;
	}

	function Pagination({
							swiper,
							extendParams,
							on,
							emit
						}) {
		const pfx = 'swiper-pagination';
		extendParams({
			pagination: {
				el: null,
				bulletElement: 'span',
				clickable: false,
				hideOnClick: false,
				renderBullet: null,
				renderProgressbar: null,
				renderFraction: null,
				renderCustom: null,
				progressbarOpposite: false,
				type: 'bullets',
				// 'bullets' or 'progressbar' or 'fraction' or 'custom'
				dynamicBullets: false,
				dynamicMainBullets: 1,
				formatFractionCurrent: number => number,
				formatFractionTotal: number => number,
				bulletClass: `${pfx}-bullet`,
				bulletActiveClass: `${pfx}-bullet-active`,
				modifierClass: `${pfx}-`,
				currentClass: `${pfx}-current`,
				totalClass: `${pfx}-total`,
				hiddenClass: `${pfx}-hidden`,
				progressbarFillClass: `${pfx}-progressbar-fill`,
				progressbarOppositeClass: `${pfx}-progressbar-opposite`,
				clickableClass: `${pfx}-clickable`,
				lockClass: `${pfx}-lock`,
				horizontalClass: `${pfx}-horizontal`,
				verticalClass: `${pfx}-vertical`,
				paginationDisabledClass: `${pfx}-disabled`
			}
		});
		swiper.pagination = {
			el: null,
			bullets: []
		};
		let bulletSize;
		let dynamicBulletIndex = 0;
		function isPaginationDisabled() {
			return !swiper.params.pagination.el || !swiper.pagination.el || Array.isArray(swiper.pagination.el) && swiper.pagination.el.length === 0;
		}
		function setSideBullets(bulletEl, position) {
			const {
				bulletActiveClass
			} = swiper.params.pagination;
			if (!bulletEl) return;
			bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`];
			if (bulletEl) {
				bulletEl.classList.add(`${bulletActiveClass}-${position}`);
				bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`];
				if (bulletEl) {
					bulletEl.classList.add(`${bulletActiveClass}-${position}-${position}`);
				}
			}
		}
		function getMoveDirection(prevIndex, nextIndex, length) {
			prevIndex = prevIndex % length;
			nextIndex = nextIndex % length;
			if (nextIndex === prevIndex + 1) {
				return 'next';
			} else if (nextIndex === prevIndex - 1) {
				return 'previous';
			}
			return;
		}
		function onBulletClick(e) {
			const bulletEl = e.target.closest(classesToSelector(swiper.params.pagination.bulletClass));
			if (!bulletEl) {
				return;
			}
			e.preventDefault();
			const index = elementIndex(bulletEl) * swiper.params.slidesPerGroup;
			if (swiper.params.loop) {
				if (swiper.realIndex === index) return;
				const moveDirection = getMoveDirection(swiper.realIndex, index, swiper.slides.length);
				if (moveDirection === 'next') {
					swiper.slideNext();
				} else if (moveDirection === 'previous') {
					swiper.slidePrev();
				} else {
					swiper.slideToLoop(index);
				}
			} else {
				swiper.slideTo(index);
			}
		}
		function update() {
			// Render || Update Pagination bullets/items
			const rtl = swiper.rtl;
			const params = swiper.params.pagination;
			if (isPaginationDisabled()) return;
			let el = swiper.pagination.el;
			el = makeElementsArray(el);
			// Current/Total
			let current;
			let previousIndex;
			const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.slides.length;
			const total = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length;
			if (swiper.params.loop) {
				previousIndex = swiper.previousRealIndex || 0;
				current = swiper.params.slidesPerGroup > 1 ? Math.floor(swiper.realIndex / swiper.params.slidesPerGroup) : swiper.realIndex;
			} else if (typeof swiper.snapIndex !== 'undefined') {
				current = swiper.snapIndex;
				previousIndex = swiper.previousSnapIndex;
			} else {
				previousIndex = swiper.previousIndex || 0;
				current = swiper.activeIndex || 0;
			}
			// Types
			if (params.type === 'bullets' && swiper.pagination.bullets && swiper.pagination.bullets.length > 0) {
				const bullets = swiper.pagination.bullets;
				let firstIndex;
				let lastIndex;
				let midIndex;
				if (params.dynamicBullets) {
					bulletSize = elementOuterSize(bullets[0], swiper.isHorizontal() ? 'width' : 'height', true);
					el.forEach(subEl => {
						subEl.style[swiper.isHorizontal() ? 'width' : 'height'] = `${bulletSize * (params.dynamicMainBullets + 4)}px`;
					});
					if (params.dynamicMainBullets > 1 && previousIndex !== undefined) {
						dynamicBulletIndex += current - (previousIndex || 0);
						if (dynamicBulletIndex > params.dynamicMainBullets - 1) {
							dynamicBulletIndex = params.dynamicMainBullets - 1;
						} else if (dynamicBulletIndex < 0) {
							dynamicBulletIndex = 0;
						}
					}
					firstIndex = Math.max(current - dynamicBulletIndex, 0);
					lastIndex = firstIndex + (Math.min(bullets.length, params.dynamicMainBullets) - 1);
					midIndex = (lastIndex + firstIndex) / 2;
				}
				bullets.forEach(bulletEl => {
					const classesToRemove = [...['', '-next', '-next-next', '-prev', '-prev-prev', '-main'].map(suffix => `${params.bulletActiveClass}${suffix}`)].map(s => typeof s === 'string' && s.includes(' ') ? s.split(' ') : s).flat();
					bulletEl.classList.remove(...classesToRemove);
				});
				if (el.length > 1) {
					bullets.forEach(bullet => {
						const bulletIndex = elementIndex(bullet);
						if (bulletIndex === current) {
							bullet.classList.add(...params.bulletActiveClass.split(' '));
						} else if (swiper.isElement) {
							bullet.setAttribute('part', 'bullet');
						}
						if (params.dynamicBullets) {
							if (bulletIndex >= firstIndex && bulletIndex <= lastIndex) {
								bullet.classList.add(...`${params.bulletActiveClass}-main`.split(' '));
							}
							if (bulletIndex === firstIndex) {
								setSideBullets(bullet, 'prev');
							}
							if (bulletIndex === lastIndex) {
								setSideBullets(bullet, 'next');
							}
						}
					});
				} else {
					const bullet = bullets[current];
					if (bullet) {
						bullet.classList.add(...params.bulletActiveClass.split(' '));
					}
					if (swiper.isElement) {
						bullets.forEach((bulletEl, bulletIndex) => {
							bulletEl.setAttribute('part', bulletIndex === current ? 'bullet-active' : 'bullet');
						});
					}
					if (params.dynamicBullets) {
						const firstDisplayedBullet = bullets[firstIndex];
						const lastDisplayedBullet = bullets[lastIndex];
						for (let i = firstIndex; i <= lastIndex; i += 1) {
							if (bullets[i]) {
								bullets[i].classList.add(...`${params.bulletActiveClass}-main`.split(' '));
							}
						}
						setSideBullets(firstDisplayedBullet, 'prev');
						setSideBullets(lastDisplayedBullet, 'next');
					}
				}
				if (params.dynamicBullets) {
					const dynamicBulletsLength = Math.min(bullets.length, params.dynamicMainBullets + 4);
					const bulletsOffset = (bulletSize * dynamicBulletsLength - bulletSize) / 2 - midIndex * bulletSize;
					const offsetProp = rtl ? 'right' : 'left';
					bullets.forEach(bullet => {
						bullet.style[swiper.isHorizontal() ? offsetProp : 'top'] = `${bulletsOffset}px`;
					});
				}
			}
			el.forEach((subEl, subElIndex) => {
				if (params.type === 'fraction') {
					subEl.querySelectorAll(classesToSelector(params.currentClass)).forEach(fractionEl => {
						fractionEl.textContent = params.formatFractionCurrent(current + 1);
					});
					subEl.querySelectorAll(classesToSelector(params.totalClass)).forEach(totalEl => {
						totalEl.textContent = params.formatFractionTotal(total);
					});
				}
				if (params.type === 'progressbar') {
					let progressbarDirection;
					if (params.progressbarOpposite) {
						progressbarDirection = swiper.isHorizontal() ? 'vertical' : 'horizontal';
					} else {
						progressbarDirection = swiper.isHorizontal() ? 'horizontal' : 'vertical';
					}
					const scale = (current + 1) / total;
					let scaleX = 1;
					let scaleY = 1;
					if (progressbarDirection === 'horizontal') {
						scaleX = scale;
					} else {
						scaleY = scale;
					}
					subEl.querySelectorAll(classesToSelector(params.progressbarFillClass)).forEach(progressEl => {
						progressEl.style.transform = `translate3d(0,0,0) scaleX(${scaleX}) scaleY(${scaleY})`;
						progressEl.style.transitionDuration = `${swiper.params.speed}ms`;
					});
				}
				if (params.type === 'custom' && params.renderCustom) {
					setInnerHTML(subEl, params.renderCustom(swiper, current + 1, total));
					if (subElIndex === 0) emit('paginationRender', subEl);
				} else {
					if (subElIndex === 0) emit('paginationRender', subEl);
					emit('paginationUpdate', subEl);
				}
				if (swiper.params.watchOverflow && swiper.enabled) {
					subEl.classList[swiper.isLocked ? 'add' : 'remove'](params.lockClass);
				}
			});
		}
		function render() {
			// Render Container
			const params = swiper.params.pagination;
			if (isPaginationDisabled()) return;
			const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.grid && swiper.params.grid.rows > 1 ? swiper.slides.length / Math.ceil(swiper.params.grid.rows) : swiper.slides.length;
			let el = swiper.pagination.el;
			el = makeElementsArray(el);
			let paginationHTML = '';
			if (params.type === 'bullets') {
				let numberOfBullets = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length;
				if (swiper.params.freeMode && swiper.params.freeMode.enabled && numberOfBullets > slidesLength) {
					numberOfBullets = slidesLength;
				}
				for (let i = 0; i < numberOfBullets; i += 1) {
					if (params.renderBullet) {
						paginationHTML += params.renderBullet.call(swiper, i, params.bulletClass);
					} else {
						// prettier-ignore
						paginationHTML += `<${params.bulletElement} ${swiper.isElement ? 'part="bullet"' : ''} class="${params.bulletClass}"></${params.bulletElement}>`;
					}
				}
			}
			if (params.type === 'fraction') {
				if (params.renderFraction) {
					paginationHTML = params.renderFraction.call(swiper, params.currentClass, params.totalClass);
				} else {
					paginationHTML = `<span class="${params.currentClass}"></span>` + ' / ' + `<span class="${params.totalClass}"></span>`;
				}
			}
			if (params.type === 'progressbar') {
				if (params.renderProgressbar) {
					paginationHTML = params.renderProgressbar.call(swiper, params.progressbarFillClass);
				} else {
					paginationHTML = `<span class="${params.progressbarFillClass}"></span>`;
				}
			}
			swiper.pagination.bullets = [];
			el.forEach(subEl => {
				if (params.type !== 'custom') {
					setInnerHTML(subEl, paginationHTML || '');
				}
				if (params.type === 'bullets') {
					swiper.pagination.bullets.push(...subEl.querySelectorAll(classesToSelector(params.bulletClass)));
				}
			});
			if (params.type !== 'custom') {
				emit('paginationRender', el[0]);
			}
		}
		function init() {
			swiper.params.pagination = createElementIfNotDefined(swiper, swiper.originalParams.pagination, swiper.params.pagination, {
				el: 'swiper-pagination'
			});
			const params = swiper.params.pagination;
			if (!params.el) return;
			let el;
			if (typeof params.el === 'string' && swiper.isElement) {
				el = swiper.el.querySelector(params.el);
			}
			if (!el && typeof params.el === 'string') {
				el = [...document.querySelectorAll(params.el)];
			}
			if (!el) {
				el = params.el;
			}
			if (!el || el.length === 0) return;
			if (swiper.params.uniqueNavElements && typeof params.el === 'string' && Array.isArray(el) && el.length > 1) {
				el = [...swiper.el.querySelectorAll(params.el)];
				// check if it belongs to another nested Swiper
				if (el.length > 1) {
					el = el.find(subEl => {
						if (elementParents(subEl, '.swiper')[0] !== swiper.el) return false;
						return true;
					});
				}
			}
			if (Array.isArray(el) && el.length === 1) el = el[0];
			Object.assign(swiper.pagination, {
				el
			});
			el = makeElementsArray(el);
			el.forEach(subEl => {
				if (params.type === 'bullets' && params.clickable) {
					subEl.classList.add(...(params.clickableClass || '').split(' '));
				}
				subEl.classList.add(params.modifierClass + params.type);
				subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
				if (params.type === 'bullets' && params.dynamicBullets) {
					subEl.classList.add(`${params.modifierClass}${params.type}-dynamic`);
					dynamicBulletIndex = 0;
					if (params.dynamicMainBullets < 1) {
						params.dynamicMainBullets = 1;
					}
				}
				if (params.type === 'progressbar' && params.progressbarOpposite) {
					subEl.classList.add(params.progressbarOppositeClass);
				}
				if (params.clickable) {
					subEl.addEventListener('click', onBulletClick);
				}
				if (!swiper.enabled) {
					subEl.classList.add(params.lockClass);
				}
			});
		}
		function destroy() {
			const params = swiper.params.pagination;
			if (isPaginationDisabled()) return;
			let el = swiper.pagination.el;
			if (el) {
				el = makeElementsArray(el);
				el.forEach(subEl => {
					subEl.classList.remove(params.hiddenClass);
					subEl.classList.remove(params.modifierClass + params.type);
					subEl.classList.remove(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
					if (params.clickable) {
						subEl.classList.remove(...(params.clickableClass || '').split(' '));
						subEl.removeEventListener('click', onBulletClick);
					}
				});
			}
			if (swiper.pagination.bullets) swiper.pagination.bullets.forEach(subEl => subEl.classList.remove(...params.bulletActiveClass.split(' ')));
		}
		on('changeDirection', () => {
			if (!swiper.pagination || !swiper.pagination.el) return;
			const params = swiper.params.pagination;
			let {
				el
			} = swiper.pagination;
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.classList.remove(params.horizontalClass, params.verticalClass);
				subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
			});
		});
		on('init', () => {
			if (swiper.params.pagination.enabled === false) {
				// eslint-disable-next-line
				disable();
			} else {
				init();
				render();
				update();
			}
		});
		on('activeIndexChange', () => {
			if (typeof swiper.snapIndex === 'undefined') {
				update();
			}
		});
		on('snapIndexChange', () => {
			update();
		});
		on('snapGridLengthChange', () => {
			render();
			update();
		});
		on('destroy', () => {
			destroy();
		});
		on('enable disable', () => {
			let {
				el
			} = swiper.pagination;
			if (el) {
				el = makeElementsArray(el);
				el.forEach(subEl => subEl.classList[swiper.enabled ? 'remove' : 'add'](swiper.params.pagination.lockClass));
			}
		});
		on('lock unlock', () => {
			update();
		});
		on('click', (_s, e) => {
			const targetEl = e.target;
			const el = makeElementsArray(swiper.pagination.el);
			if (swiper.params.pagination.el && swiper.params.pagination.hideOnClick && el && el.length > 0 && !targetEl.classList.contains(swiper.params.pagination.bulletClass)) {
				if (swiper.navigation && (swiper.navigation.nextEl && targetEl === swiper.navigation.nextEl || swiper.navigation.prevEl && targetEl === swiper.navigation.prevEl)) return;
				const isHidden = el[0].classList.contains(swiper.params.pagination.hiddenClass);
				if (isHidden === true) {
					emit('paginationShow');
				} else {
					emit('paginationHide');
				}
				el.forEach(subEl => subEl.classList.toggle(swiper.params.pagination.hiddenClass));
			}
		});
		const enable = () => {
			swiper.el.classList.remove(swiper.params.pagination.paginationDisabledClass);
			let {
				el
			} = swiper.pagination;
			if (el) {
				el = makeElementsArray(el);
				el.forEach(subEl => subEl.classList.remove(swiper.params.pagination.paginationDisabledClass));
			}
			init();
			render();
			update();
		};
		const disable = () => {
			swiper.el.classList.add(swiper.params.pagination.paginationDisabledClass);
			let {
				el
			} = swiper.pagination;
			if (el) {
				el = makeElementsArray(el);
				el.forEach(subEl => subEl.classList.add(swiper.params.pagination.paginationDisabledClass));
			}
			destroy();
		};
		Object.assign(swiper.pagination, {
			enable,
			disable,
			render,
			update,
			init,
			destroy
		});
	}

	function Scrollbar({
						   swiper,
						   extendParams,
						   on,
						   emit
					   }) {
		const document = getDocument();
		let isTouched = false;
		let timeout = null;
		let dragTimeout = null;
		let dragStartPos;
		let dragSize;
		let trackSize;
		let divider;
		extendParams({
			scrollbar: {
				el: null,
				dragSize: 'auto',
				hide: false,
				draggable: false,
				snapOnRelease: true,
				lockClass: 'swiper-scrollbar-lock',
				dragClass: 'swiper-scrollbar-drag',
				scrollbarDisabledClass: 'swiper-scrollbar-disabled',
				horizontalClass: `swiper-scrollbar-horizontal`,
				verticalClass: `swiper-scrollbar-vertical`
			}
		});
		swiper.scrollbar = {
			el: null,
			dragEl: null
		};
		function setTranslate() {
			if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
			const {
				scrollbar,
				rtlTranslate: rtl
			} = swiper;
			const {
				dragEl,
				el
			} = scrollbar;
			const params = swiper.params.scrollbar;
			const progress = swiper.params.loop ? swiper.progressLoop : swiper.progress;
			let newSize = dragSize;
			let newPos = (trackSize - dragSize) * progress;
			if (rtl) {
				newPos = -newPos;
				if (newPos > 0) {
					newSize = dragSize - newPos;
					newPos = 0;
				} else if (-newPos + dragSize > trackSize) {
					newSize = trackSize + newPos;
				}
			} else if (newPos < 0) {
				newSize = dragSize + newPos;
				newPos = 0;
			} else if (newPos + dragSize > trackSize) {
				newSize = trackSize - newPos;
			}
			if (swiper.isHorizontal()) {
				dragEl.style.transform = `translate3d(${newPos}px, 0, 0)`;
				dragEl.style.width = `${newSize}px`;
			} else {
				dragEl.style.transform = `translate3d(0px, ${newPos}px, 0)`;
				dragEl.style.height = `${newSize}px`;
			}
			if (params.hide) {
				clearTimeout(timeout);
				el.style.opacity = 1;
				timeout = setTimeout(() => {
					el.style.opacity = 0;
					el.style.transitionDuration = '400ms';
				}, 1000);
			}
		}
		function setTransition(duration) {
			if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
			swiper.scrollbar.dragEl.style.transitionDuration = `${duration}ms`;
		}
		function updateSize() {
			if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
			const {
				scrollbar
			} = swiper;
			const {
				dragEl,
				el
			} = scrollbar;
			dragEl.style.width = '';
			dragEl.style.height = '';
			trackSize = swiper.isHorizontal() ? el.offsetWidth : el.offsetHeight;
			divider = swiper.size / (swiper.virtualSize + swiper.params.slidesOffsetBefore - (swiper.params.centeredSlides ? swiper.snapGrid[0] : 0));
			if (swiper.params.scrollbar.dragSize === 'auto') {
				dragSize = trackSize * divider;
			} else {
				dragSize = parseInt(swiper.params.scrollbar.dragSize, 10);
			}
			if (swiper.isHorizontal()) {
				dragEl.style.width = `${dragSize}px`;
			} else {
				dragEl.style.height = `${dragSize}px`;
			}
			if (divider >= 1) {
				el.style.display = 'none';
			} else {
				el.style.display = '';
			}
			if (swiper.params.scrollbar.hide) {
				el.style.opacity = 0;
			}
			if (swiper.params.watchOverflow && swiper.enabled) {
				scrollbar.el.classList[swiper.isLocked ? 'add' : 'remove'](swiper.params.scrollbar.lockClass);
			}
		}
		function getPointerPosition(e) {
			return swiper.isHorizontal() ? e.clientX : e.clientY;
		}
		function setDragPosition(e) {
			const {
				scrollbar,
				rtlTranslate: rtl
			} = swiper;
			const {
				el
			} = scrollbar;
			let positionRatio;
			positionRatio = (getPointerPosition(e) - elementOffset(el)[swiper.isHorizontal() ? 'left' : 'top'] - (dragStartPos !== null ? dragStartPos : dragSize / 2)) / (trackSize - dragSize);
			positionRatio = Math.max(Math.min(positionRatio, 1), 0);
			if (rtl) {
				positionRatio = 1 - positionRatio;
			}
			const position = swiper.minTranslate() + (swiper.maxTranslate() - swiper.minTranslate()) * positionRatio;
			swiper.updateProgress(position);
			swiper.setTranslate(position);
			swiper.updateActiveIndex();
			swiper.updateSlidesClasses();
		}
		function onDragStart(e) {
			const params = swiper.params.scrollbar;
			const {
				scrollbar,
				wrapperEl
			} = swiper;
			const {
				el,
				dragEl
			} = scrollbar;
			isTouched = true;
			dragStartPos = e.target === dragEl ? getPointerPosition(e) - e.target.getBoundingClientRect()[swiper.isHorizontal() ? 'left' : 'top'] : null;
			e.preventDefault();
			e.stopPropagation();
			wrapperEl.style.transitionDuration = '100ms';
			dragEl.style.transitionDuration = '100ms';
			setDragPosition(e);
			clearTimeout(dragTimeout);
			el.style.transitionDuration = '0ms';
			if (params.hide) {
				el.style.opacity = 1;
			}
			if (swiper.params.cssMode) {
				swiper.wrapperEl.style['scroll-snap-type'] = 'none';
			}
			emit('scrollbarDragStart', e);
		}
		function onDragMove(e) {
			const {
				scrollbar,
				wrapperEl
			} = swiper;
			const {
				el,
				dragEl
			} = scrollbar;
			if (!isTouched) return;
			if (e.preventDefault && e.cancelable) e.preventDefault();else e.returnValue = false;
			setDragPosition(e);
			wrapperEl.style.transitionDuration = '0ms';
			el.style.transitionDuration = '0ms';
			dragEl.style.transitionDuration = '0ms';
			emit('scrollbarDragMove', e);
		}
		function onDragEnd(e) {
			const params = swiper.params.scrollbar;
			const {
				scrollbar,
				wrapperEl
			} = swiper;
			const {
				el
			} = scrollbar;
			if (!isTouched) return;
			isTouched = false;
			if (swiper.params.cssMode) {
				swiper.wrapperEl.style['scroll-snap-type'] = '';
				wrapperEl.style.transitionDuration = '';
			}
			if (params.hide) {
				clearTimeout(dragTimeout);
				dragTimeout = nextTick(() => {
					el.style.opacity = 0;
					el.style.transitionDuration = '400ms';
				}, 1000);
			}
			emit('scrollbarDragEnd', e);
			if (params.snapOnRelease) {
				swiper.slideToClosest();
			}
		}
		function events(method) {
			const {
				scrollbar,
				params
			} = swiper;
			const el = scrollbar.el;
			if (!el) return;
			const target = el;
			const activeListener = params.passiveListeners ? {
				passive: false,
				capture: false
			} : false;
			const passiveListener = params.passiveListeners ? {
				passive: true,
				capture: false
			} : false;
			if (!target) return;
			const eventMethod = method === 'on' ? 'addEventListener' : 'removeEventListener';
			target[eventMethod]('pointerdown', onDragStart, activeListener);
			document[eventMethod]('pointermove', onDragMove, activeListener);
			document[eventMethod]('pointerup', onDragEnd, passiveListener);
		}
		function enableDraggable() {
			if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
			events('on');
		}
		function disableDraggable() {
			if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
			events('off');
		}
		function init() {
			const {
				scrollbar,
				el: swiperEl
			} = swiper;
			swiper.params.scrollbar = createElementIfNotDefined(swiper, swiper.originalParams.scrollbar, swiper.params.scrollbar, {
				el: 'swiper-scrollbar'
			});
			const params = swiper.params.scrollbar;
			if (!params.el) return;
			let el;
			if (typeof params.el === 'string' && swiper.isElement) {
				el = swiper.el.querySelector(params.el);
			}
			if (!el && typeof params.el === 'string') {
				el = document.querySelectorAll(params.el);
				if (!el.length) return;
			} else if (!el) {
				el = params.el;
			}
			if (swiper.params.uniqueNavElements && typeof params.el === 'string' && el.length > 1 && swiperEl.querySelectorAll(params.el).length === 1) {
				el = swiperEl.querySelector(params.el);
			}
			if (el.length > 0) el = el[0];
			el.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
			let dragEl;
			if (el) {
				dragEl = el.querySelector(classesToSelector(swiper.params.scrollbar.dragClass));
				if (!dragEl) {
					dragEl = createElement('div', swiper.params.scrollbar.dragClass);
					el.append(dragEl);
				}
			}
			Object.assign(scrollbar, {
				el,
				dragEl
			});
			if (params.draggable) {
				enableDraggable();
			}
			if (el) {
				el.classList[swiper.enabled ? 'remove' : 'add'](...classesToTokens(swiper.params.scrollbar.lockClass));
			}
		}
		function destroy() {
			const params = swiper.params.scrollbar;
			const el = swiper.scrollbar.el;
			if (el) {
				el.classList.remove(...classesToTokens(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass));
			}
			disableDraggable();
		}
		on('changeDirection', () => {
			if (!swiper.scrollbar || !swiper.scrollbar.el) return;
			const params = swiper.params.scrollbar;
			let {
				el
			} = swiper.scrollbar;
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.classList.remove(params.horizontalClass, params.verticalClass);
				subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
			});
		});
		on('init', () => {
			if (swiper.params.scrollbar.enabled === false) {
				// eslint-disable-next-line
				disable();
			} else {
				init();
				updateSize();
				setTranslate();
			}
		});
		on('update resize observerUpdate lock unlock changeDirection', () => {
			updateSize();
		});
		on('setTranslate', () => {
			setTranslate();
		});
		on('setTransition', (_s, duration) => {
			setTransition(duration);
		});
		on('enable disable', () => {
			const {
				el
			} = swiper.scrollbar;
			if (el) {
				el.classList[swiper.enabled ? 'remove' : 'add'](...classesToTokens(swiper.params.scrollbar.lockClass));
			}
		});
		on('destroy', () => {
			destroy();
		});
		const enable = () => {
			swiper.el.classList.remove(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
			if (swiper.scrollbar.el) {
				swiper.scrollbar.el.classList.remove(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
			}
			init();
			updateSize();
			setTranslate();
		};
		const disable = () => {
			swiper.el.classList.add(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
			if (swiper.scrollbar.el) {
				swiper.scrollbar.el.classList.add(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
			}
			destroy();
		};
		Object.assign(swiper.scrollbar, {
			enable,
			disable,
			updateSize,
			setTranslate,
			init,
			destroy
		});
	}

	function Parallax({
						  swiper,
						  extendParams,
						  on
					  }) {
		extendParams({
			parallax: {
				enabled: false
			}
		});
		const elementsSelector = '[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y], [data-swiper-parallax-opacity], [data-swiper-parallax-scale]';
		const setTransform = (el, progress) => {
			const {
				rtl
			} = swiper;
			const rtlFactor = rtl ? -1 : 1;
			const p = el.getAttribute('data-swiper-parallax') || '0';
			let x = el.getAttribute('data-swiper-parallax-x');
			let y = el.getAttribute('data-swiper-parallax-y');
			const scale = el.getAttribute('data-swiper-parallax-scale');
			const opacity = el.getAttribute('data-swiper-parallax-opacity');
			const rotate = el.getAttribute('data-swiper-parallax-rotate');
			if (x || y) {
				x = x || '0';
				y = y || '0';
			} else if (swiper.isHorizontal()) {
				x = p;
				y = '0';
			} else {
				y = p;
				x = '0';
			}
			if (x.indexOf('%') >= 0) {
				x = `${parseInt(x, 10) * progress * rtlFactor}%`;
			} else {
				x = `${x * progress * rtlFactor}px`;
			}
			if (y.indexOf('%') >= 0) {
				y = `${parseInt(y, 10) * progress}%`;
			} else {
				y = `${y * progress}px`;
			}
			if (typeof opacity !== 'undefined' && opacity !== null) {
				const currentOpacity = opacity - (opacity - 1) * (1 - Math.abs(progress));
				el.style.opacity = currentOpacity;
			}
			let transform = `translate3d(${x}, ${y}, 0px)`;
			if (typeof scale !== 'undefined' && scale !== null) {
				const currentScale = scale - (scale - 1) * (1 - Math.abs(progress));
				transform += ` scale(${currentScale})`;
			}
			if (rotate && typeof rotate !== 'undefined' && rotate !== null) {
				const currentRotate = rotate * progress * -1;
				transform += ` rotate(${currentRotate}deg)`;
			}
			el.style.transform = transform;
		};
		const setTranslate = () => {
			const {
				el,
				slides,
				progress,
				snapGrid,
				isElement
			} = swiper;
			const elements = elementChildren(el, elementsSelector);
			if (swiper.isElement) {
				elements.push(...elementChildren(swiper.hostEl, elementsSelector));
			}
			elements.forEach(subEl => {
				setTransform(subEl, progress);
			});
			slides.forEach((slideEl, slideIndex) => {
				let slideProgress = slideEl.progress;
				if (swiper.params.slidesPerGroup > 1 && swiper.params.slidesPerView !== 'auto') {
					slideProgress += Math.ceil(slideIndex / 2) - progress * (snapGrid.length - 1);
				}
				slideProgress = Math.min(Math.max(slideProgress, -1), 1);
				slideEl.querySelectorAll(`${elementsSelector}, [data-swiper-parallax-rotate]`).forEach(subEl => {
					setTransform(subEl, slideProgress);
				});
			});
		};
		const setTransition = (duration = swiper.params.speed) => {
			const {
				el,
				hostEl
			} = swiper;
			const elements = [...el.querySelectorAll(elementsSelector)];
			if (swiper.isElement) {
				elements.push(...hostEl.querySelectorAll(elementsSelector));
			}
			elements.forEach(parallaxEl => {
				let parallaxDuration = parseInt(parallaxEl.getAttribute('data-swiper-parallax-duration'), 10) || duration;
				if (duration === 0) parallaxDuration = 0;
				parallaxEl.style.transitionDuration = `${parallaxDuration}ms`;
			});
		};
		on('beforeInit', () => {
			if (!swiper.params.parallax.enabled) return;
			swiper.params.watchSlidesProgress = true;
			swiper.originalParams.watchSlidesProgress = true;
		});
		on('init', () => {
			if (!swiper.params.parallax.enabled) return;
			setTranslate();
		});
		on('setTranslate', () => {
			if (!swiper.params.parallax.enabled) return;
			setTranslate();
		});
		on('setTransition', (_swiper, duration) => {
			if (!swiper.params.parallax.enabled) return;
			setTransition(duration);
		});
	}

	/* eslint no-bitwise: ["error", { "allow": [">>"] }] */
	function Controller({
							swiper,
							extendParams,
							on
						}) {
		extendParams({
			controller: {
				control: undefined,
				inverse: false,
				by: 'slide' // or 'container'
			}
		});

		swiper.controller = {
			control: undefined
		};
		function LinearSpline(x, y) {
			const binarySearch = function search() {
				let maxIndex;
				let minIndex;
				let guess;
				return (array, val) => {
					minIndex = -1;
					maxIndex = array.length;
					while (maxIndex - minIndex > 1) {
						guess = maxIndex + minIndex >> 1;
						if (array[guess] <= val) {
							minIndex = guess;
						} else {
							maxIndex = guess;
						}
					}
					return maxIndex;
				};
			}();
			this.x = x;
			this.y = y;
			this.lastIndex = x.length - 1;
			// Given an x value (x2), return the expected y2 value:
			// (x1,y1) is the known point before given value,
			// (x3,y3) is the known point after given value.
			let i1;
			let i3;
			this.interpolate = function interpolate(x2) {
				if (!x2) return 0;

				// Get the indexes of x1 and x3 (the array indexes before and after given x2):
				i3 = binarySearch(this.x, x2);
				i1 = i3 - 1;

				// We have our indexes i1 & i3, so we can calculate already:
				// y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1
				return (x2 - this.x[i1]) * (this.y[i3] - this.y[i1]) / (this.x[i3] - this.x[i1]) + this.y[i1];
			};
			return this;
		}
		function getInterpolateFunction(c) {
			swiper.controller.spline = swiper.params.loop ? new LinearSpline(swiper.slidesGrid, c.slidesGrid) : new LinearSpline(swiper.snapGrid, c.snapGrid);
		}
		function setTranslate(_t, byController) {
			const controlled = swiper.controller.control;
			let multiplier;
			let controlledTranslate;
			const Swiper = swiper.constructor;
			function setControlledTranslate(c) {
				if (c.destroyed) return;

				// this will create an Interpolate function based on the snapGrids
				// x is the Grid of the scrolled scroller and y will be the controlled scroller
				// it makes sense to create this only once and recall it for the interpolation
				// the function does a lot of value caching for performance
				const translate = swiper.rtlTranslate ? -swiper.translate : swiper.translate;
				if (swiper.params.controller.by === 'slide') {
					getInterpolateFunction(c);
					// i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid
					// but it did not work out
					controlledTranslate = -swiper.controller.spline.interpolate(-translate);
				}
				if (!controlledTranslate || swiper.params.controller.by === 'container') {
					multiplier = (c.maxTranslate() - c.minTranslate()) / (swiper.maxTranslate() - swiper.minTranslate());
					if (Number.isNaN(multiplier) || !Number.isFinite(multiplier)) {
						multiplier = 1;
					}
					controlledTranslate = (translate - swiper.minTranslate()) * multiplier + c.minTranslate();
				}
				if (swiper.params.controller.inverse) {
					controlledTranslate = c.maxTranslate() - controlledTranslate;
				}
				c.updateProgress(controlledTranslate);
				c.setTranslate(controlledTranslate, swiper);
				c.updateActiveIndex();
				c.updateSlidesClasses();
			}
			if (Array.isArray(controlled)) {
				for (let i = 0; i < controlled.length; i += 1) {
					if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
						setControlledTranslate(controlled[i]);
					}
				}
			} else if (controlled instanceof Swiper && byController !== controlled) {
				setControlledTranslate(controlled);
			}
		}
		function setTransition(duration, byController) {
			const Swiper = swiper.constructor;
			const controlled = swiper.controller.control;
			let i;
			function setControlledTransition(c) {
				if (c.destroyed) return;
				c.setTransition(duration, swiper);
				if (duration !== 0) {
					c.transitionStart();
					if (c.params.autoHeight) {
						nextTick(() => {
							c.updateAutoHeight();
						});
					}
					elementTransitionEnd(c.wrapperEl, () => {
						if (!controlled) return;
						c.transitionEnd();
					});
				}
			}
			if (Array.isArray(controlled)) {
				for (i = 0; i < controlled.length; i += 1) {
					if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
						setControlledTransition(controlled[i]);
					}
				}
			} else if (controlled instanceof Swiper && byController !== controlled) {
				setControlledTransition(controlled);
			}
		}
		function removeSpline() {
			if (!swiper.controller.control) return;
			if (swiper.controller.spline) {
				swiper.controller.spline = undefined;
				delete swiper.controller.spline;
			}
		}
		on('beforeInit', () => {
			if (typeof window !== 'undefined' && (
				// eslint-disable-line
				typeof swiper.params.controller.control === 'string' || swiper.params.controller.control instanceof HTMLElement)) {
				const controlElements = typeof swiper.params.controller.control === 'string' ? [...document.querySelectorAll(swiper.params.controller.control)] : [swiper.params.controller.control];
				controlElements.forEach(controlElement => {
					if (!swiper.controller.control) swiper.controller.control = [];
					if (controlElement && controlElement.swiper) {
						swiper.controller.control.push(controlElement.swiper);
					} else if (controlElement) {
						const eventName = `${swiper.params.eventsPrefix}init`;
						const onControllerSwiper = e => {
							swiper.controller.control.push(e.detail[0]);
							swiper.update();
							controlElement.removeEventListener(eventName, onControllerSwiper);
						};
						controlElement.addEventListener(eventName, onControllerSwiper);
					}
				});
				return;
			}
			swiper.controller.control = swiper.params.controller.control;
		});
		on('update', () => {
			removeSpline();
		});
		on('resize', () => {
			removeSpline();
		});
		on('observerUpdate', () => {
			removeSpline();
		});
		on('setTranslate', (_s, translate, byController) => {
			if (!swiper.controller.control || swiper.controller.control.destroyed) return;
			swiper.controller.setTranslate(translate, byController);
		});
		on('setTransition', (_s, duration, byController) => {
			if (!swiper.controller.control || swiper.controller.control.destroyed) return;
			swiper.controller.setTransition(duration, byController);
		});
		Object.assign(swiper.controller, {
			setTranslate,
			setTransition
		});
	}

	function A11y({
					  swiper,
					  extendParams,
					  on
				  }) {
		extendParams({
			a11y: {
				enabled: true,
				notificationClass: 'swiper-notification',
				prevSlideMessage: 'Previous slide',
				nextSlideMessage: 'Next slide',
				firstSlideMessage: 'This is the first slide',
				lastSlideMessage: 'This is the last slide',
				paginationBulletMessage: 'Go to slide {{index}}',
				slideLabelMessage: '{{index}} / {{slidesLength}}',
				containerMessage: null,
				containerRoleDescriptionMessage: null,
				containerRole: null,
				itemRoleDescriptionMessage: null,
				slideRole: 'group',
				id: null,
				scrollOnFocus: true,
				wrapperLiveRegion: true
			}
		});
		swiper.a11y = {
			clicked: false
		};
		let liveRegion = null;
		let preventFocusHandler;
		let focusTargetSlideEl;
		let visibilityChangedTimestamp = new Date().getTime();
		function notify(message) {
			const notification = liveRegion;
			if (notification.length === 0) return;
			setInnerHTML(notification, message);
		}
		function getRandomNumber(size = 16) {
			const randomChar = () => Math.round(16 * Math.random()).toString(16);
			return 'x'.repeat(size).replace(/x/g, randomChar);
		}
		function makeElFocusable(el) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('tabIndex', '0');
			});
		}
		function makeElNotFocusable(el) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('tabIndex', '-1');
			});
		}
		function addElRole(el, role) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('role', role);
			});
		}
		function addElRoleDescription(el, description) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('aria-roledescription', description);
			});
		}
		function addElControls(el, controls) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('aria-controls', controls);
			});
		}
		function addElLabel(el, label) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('aria-label', label);
			});
		}
		function addElId(el, id) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('id', id);
			});
		}
		function addElLive(el, live) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('aria-live', live);
			});
		}
		function disableEl(el) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('aria-disabled', true);
			});
		}
		function enableEl(el) {
			el = makeElementsArray(el);
			el.forEach(subEl => {
				subEl.setAttribute('aria-disabled', false);
			});
		}
		function onEnterOrSpaceKey(e) {
			if (e.keyCode !== 13 && e.keyCode !== 32) return;
			const params = swiper.params.a11y;
			const targetEl = e.target;
			if (swiper.pagination && swiper.pagination.el && (targetEl === swiper.pagination.el || swiper.pagination.el.contains(e.target))) {
				if (!e.target.matches(classesToSelector(swiper.params.pagination.bulletClass))) return;
			}
			if (swiper.navigation && swiper.navigation.prevEl && swiper.navigation.nextEl) {
				const prevEls = makeElementsArray(swiper.navigation.prevEl);
				const nextEls = makeElementsArray(swiper.navigation.nextEl);
				if (nextEls.includes(targetEl)) {
					if (!(swiper.isEnd && !swiper.params.loop)) {
						swiper.slideNext();
					}
					if (swiper.isEnd) {
						notify(params.lastSlideMessage);
					} else {
						notify(params.nextSlideMessage);
					}
				}
				if (prevEls.includes(targetEl)) {
					if (!(swiper.isBeginning && !swiper.params.loop)) {
						swiper.slidePrev();
					}
					if (swiper.isBeginning) {
						notify(params.firstSlideMessage);
					} else {
						notify(params.prevSlideMessage);
					}
				}
			}
			if (swiper.pagination && targetEl.matches(classesToSelector(swiper.params.pagination.bulletClass))) {
				targetEl.click();
			}
		}
		function updateNavigation() {
			if (swiper.params.loop || swiper.params.rewind || !swiper.navigation) return;
			const {
				nextEl,
				prevEl
			} = swiper.navigation;
			if (prevEl) {
				if (swiper.isBeginning) {
					disableEl(prevEl);
					makeElNotFocusable(prevEl);
				} else {
					enableEl(prevEl);
					makeElFocusable(prevEl);
				}
			}
			if (nextEl) {
				if (swiper.isEnd) {
					disableEl(nextEl);
					makeElNotFocusable(nextEl);
				} else {
					enableEl(nextEl);
					makeElFocusable(nextEl);
				}
			}
		}
		function hasPagination() {
			return swiper.pagination && swiper.pagination.bullets && swiper.pagination.bullets.length;
		}
		function hasClickablePagination() {
			return hasPagination() && swiper.params.pagination.clickable;
		}
		function updatePagination() {
			const params = swiper.params.a11y;
			if (!hasPagination()) return;
			swiper.pagination.bullets.forEach(bulletEl => {
				if (swiper.params.pagination.clickable) {
					makeElFocusable(bulletEl);
					if (!swiper.params.pagination.renderBullet) {
						addElRole(bulletEl, 'button');
						addElLabel(bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, elementIndex(bulletEl) + 1));
					}
				}
				if (bulletEl.matches(classesToSelector(swiper.params.pagination.bulletActiveClass))) {
					bulletEl.setAttribute('aria-current', 'true');
				} else {
					bulletEl.removeAttribute('aria-current');
				}
			});
		}
		const initNavEl = (el, wrapperId, message) => {
			makeElFocusable(el);
			if (el.tagName !== 'BUTTON') {
				addElRole(el, 'button');
				el.addEventListener('keydown', onEnterOrSpaceKey);
			}
			addElLabel(el, message);
			addElControls(el, wrapperId);
		};
		const handlePointerDown = e => {
			if (focusTargetSlideEl && focusTargetSlideEl !== e.target && !focusTargetSlideEl.contains(e.target)) {
				preventFocusHandler = true;
			}
			swiper.a11y.clicked = true;
		};
		const handlePointerUp = () => {
			preventFocusHandler = false;
			requestAnimationFrame(() => {
				requestAnimationFrame(() => {
					if (!swiper.destroyed) {
						swiper.a11y.clicked = false;
					}
				});
			});
		};
		const onVisibilityChange = e => {
			visibilityChangedTimestamp = new Date().getTime();
		};
		const handleFocus = e => {
			if (swiper.a11y.clicked || !swiper.params.a11y.scrollOnFocus) return;
			if (new Date().getTime() - visibilityChangedTimestamp < 100) return;
			const slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`);
			if (!slideEl || !swiper.slides.includes(slideEl)) return;
			focusTargetSlideEl = slideEl;
			const isVirtual = swiper.virtual && swiper.params.virtual.enabled;
			const isActive = (isVirtual ? parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10) : swiper.slides.indexOf(slideEl)) === swiper.activeIndex;
			const isVisible = swiper.params.watchSlidesProgress && swiper.visibleSlides && swiper.visibleSlides.includes(slideEl);
			if (isActive || isVisible) return;
			if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents) return;
			if (swiper.isHorizontal()) {
				swiper.el.scrollLeft = 0;
			} else {
				swiper.el.scrollTop = 0;
			}
			requestAnimationFrame(() => {
				if (preventFocusHandler) return;
				if (swiper.params.loop) {
					swiper.slideToLoop(swiper.getSlideIndexWhenGrid(parseInt(slideEl.getAttribute('data-swiper-slide-index'))), 0);
				} else if (isVirtual) {
					swiper.slideTo(swiper.getSlideIndexWhenGrid(parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10)), 0);
				} else {
					swiper.slideTo(swiper.getSlideIndexWhenGrid(swiper.slides.indexOf(slideEl)), 0);
				}
				preventFocusHandler = false;
			});
		};
		const initSlides = () => {
			const params = swiper.params.a11y;
			if (params.itemRoleDescriptionMessage) {
				addElRoleDescription(swiper.slides, params.itemRoleDescriptionMessage);
			}
			if (params.slideRole) {
				addElRole(swiper.slides, params.slideRole);
			}
			const slidesLength = swiper.slides.length;
			if (params.slideLabelMessage) {
				swiper.slides.forEach((slideEl, index) => {
					const slideIndex = swiper.params.loop ? parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10) : index;
					const ariaLabelMessage = params.slideLabelMessage.replace(/\{\{index\}\}/, slideIndex + 1).replace(/\{\{slidesLength\}\}/, slidesLength);
					addElLabel(slideEl, ariaLabelMessage);
				});
			}
		};
		const init = () => {
			const params = swiper.params.a11y;
			swiper.el.append(liveRegion);

			// Container
			const containerEl = swiper.el;
			if (params.containerRoleDescriptionMessage) {
				addElRoleDescription(containerEl, params.containerRoleDescriptionMessage);
			}
			if (params.containerMessage) {
				addElLabel(containerEl, params.containerMessage);
			}
			if (params.containerRole) {
				addElRole(containerEl, params.containerRole);
			}

			// Wrapper
			const wrapperEl = swiper.wrapperEl;
			const wrapperId = params.id || wrapperEl.getAttribute('id') || `swiper-wrapper-${getRandomNumber(16)}`;
			addElId(wrapperEl, wrapperId);
			if (params.wrapperLiveRegion) {
				const live = swiper.params.autoplay && swiper.params.autoplay.enabled ? 'off' : 'polite';
				addElLive(wrapperEl, live);
			}

			// Slide
			initSlides();

			// Navigation
			let {
				nextEl,
				prevEl
			} = swiper.navigation ? swiper.navigation : {};
			nextEl = makeElementsArray(nextEl);
			prevEl = makeElementsArray(prevEl);
			if (nextEl) {
				nextEl.forEach(el => initNavEl(el, wrapperId, params.nextSlideMessage));
			}
			if (prevEl) {
				prevEl.forEach(el => initNavEl(el, wrapperId, params.prevSlideMessage));
			}

			// Pagination
			if (hasClickablePagination()) {
				const paginationEl = makeElementsArray(swiper.pagination.el);
				paginationEl.forEach(el => {
					el.addEventListener('keydown', onEnterOrSpaceKey);
				});
			}

			// Tab focus
			const document = getDocument();
			document.addEventListener('visibilitychange', onVisibilityChange);
			swiper.el.addEventListener('focus', handleFocus, true);
			swiper.el.addEventListener('pointerdown', handlePointerDown, true);
			swiper.el.addEventListener('pointerup', handlePointerUp, true);
		};
		function destroy() {
			if (liveRegion) liveRegion.remove();
			let {
				nextEl,
				prevEl
			} = swiper.navigation ? swiper.navigation : {};
			nextEl = makeElementsArray(nextEl);
			prevEl = makeElementsArray(prevEl);
			if (nextEl) {
				nextEl.forEach(el => el.removeEventListener('keydown', onEnterOrSpaceKey));
			}
			if (prevEl) {
				prevEl.forEach(el => el.removeEventListener('keydown', onEnterOrSpaceKey));
			}

			// Pagination
			if (hasClickablePagination()) {
				const paginationEl = makeElementsArray(swiper.pagination.el);
				paginationEl.forEach(el => {
					el.removeEventListener('keydown', onEnterOrSpaceKey);
				});
			}
			const document = getDocument();
			document.removeEventListener('visibilitychange', onVisibilityChange);
			// Tab focus
			if (swiper.el && typeof swiper.el !== 'string') {
				swiper.el.removeEventListener('focus', handleFocus, true);
				swiper.el.removeEventListener('pointerdown', handlePointerDown, true);
				swiper.el.removeEventListener('pointerup', handlePointerUp, true);
			}
		}
		on('beforeInit', () => {
			liveRegion = createElement('span', swiper.params.a11y.notificationClass);
			liveRegion.setAttribute('aria-live', 'assertive');
			liveRegion.setAttribute('aria-atomic', 'true');
		});
		on('afterInit', () => {
			if (!swiper.params.a11y.enabled) return;
			init();
		});
		on('slidesLengthChange snapGridLengthChange slidesGridLengthChange', () => {
			if (!swiper.params.a11y.enabled) return;
			initSlides();
		});
		on('fromEdge toEdge afterInit lock unlock', () => {
			if (!swiper.params.a11y.enabled) return;
			updateNavigation();
		});
		on('paginationUpdate', () => {
			if (!swiper.params.a11y.enabled) return;
			updatePagination();
		});
		on('destroy', () => {
			if (!swiper.params.a11y.enabled) return;
			destroy();
		});
	}

	/* eslint no-use-before-define: "off" */
	function Autoplay({
						  swiper,
						  extendParams,
						  on,
						  emit,
						  params
					  }) {
		swiper.autoplay = {
			running: false,
			paused: false,
			timeLeft: 0
		};
		extendParams({
			autoplay: {
				enabled: false,
				delay: 3000,
				waitForTransition: true,
				disableOnInteraction: false,
				stopOnLastSlide: false,
				reverseDirection: false,
				pauseOnMouseEnter: false
			}
		});
		let timeout;
		let raf;
		let autoplayDelayTotal = params && params.autoplay ? params.autoplay.delay : 3000;
		let autoplayDelayCurrent = params && params.autoplay ? params.autoplay.delay : 3000;
		let autoplayTimeLeft;
		let autoplayStartTime = new Date().getTime();
		let wasPaused;
		let isTouched;
		let pausedByTouch;
		let touchStartTimeout;
		let pausedByInteraction;
		let pausedByPointerEnter;
		function onTransitionEnd(e) {
			if (!swiper || swiper.destroyed || !swiper.wrapperEl) return;
			if (e.target !== swiper.wrapperEl) return;
			swiper.wrapperEl.removeEventListener('transitionend', onTransitionEnd);
			if (pausedByPointerEnter || e.detail && e.detail.bySwiperTouchMove) {
				return;
			}
			resume();
		}
		const calcTimeLeft = () => {
			if (swiper.destroyed || !swiper.autoplay.running) return;
			if (swiper.autoplay.paused) {
				wasPaused = true;
			} else if (wasPaused) {
				autoplayDelayCurrent = autoplayTimeLeft;
				wasPaused = false;
			}
			const timeLeft = swiper.autoplay.paused ? autoplayTimeLeft : autoplayStartTime + autoplayDelayCurrent - new Date().getTime();
			swiper.autoplay.timeLeft = timeLeft;
			emit('autoplayTimeLeft', timeLeft, timeLeft / autoplayDelayTotal);
			raf = requestAnimationFrame(() => {
				calcTimeLeft();
			});
		};
		const getSlideDelay = () => {
			let activeSlideEl;
			if (swiper.virtual && swiper.params.virtual.enabled) {
				activeSlideEl = swiper.slides.find(slideEl => slideEl.classList.contains('swiper-slide-active'));
			} else {
				activeSlideEl = swiper.slides[swiper.activeIndex];
			}
			if (!activeSlideEl) return undefined;
			const currentSlideDelay = parseInt(activeSlideEl.getAttribute('data-swiper-autoplay'), 10);
			return currentSlideDelay;
		};
		const getTotalDelay = () => {
			let totalDelay = swiper.params.autoplay.delay;
			const currentSlideDelay = getSlideDelay();
			if (!Number.isNaN(currentSlideDelay) && currentSlideDelay > 0) {
				totalDelay = currentSlideDelay;
			}
			return totalDelay;
		};
		const run = delayForce => {
			if (swiper.destroyed || !swiper.autoplay.running) return;
			cancelAnimationFrame(raf);
			calcTimeLeft();
			let delay = delayForce;
			if (typeof delay === 'undefined') {
				delay = getTotalDelay();
				autoplayDelayTotal = delay;
				autoplayDelayCurrent = delay;
			}
			autoplayTimeLeft = delay;
			const speed = swiper.params.speed;
			const proceed = () => {
				if (!swiper || swiper.destroyed) return;
				if (swiper.params.autoplay.reverseDirection) {
					if (!swiper.isBeginning || swiper.params.loop || swiper.params.rewind) {
						swiper.slidePrev(speed, true, true);
						emit('autoplay');
					} else if (!swiper.params.autoplay.stopOnLastSlide) {
						swiper.slideTo(swiper.slides.length - 1, speed, true, true);
						emit('autoplay');
					}
				} else {
					if (!swiper.isEnd || swiper.params.loop || swiper.params.rewind) {
						swiper.slideNext(speed, true, true);
						emit('autoplay');
					} else if (!swiper.params.autoplay.stopOnLastSlide) {
						swiper.slideTo(0, speed, true, true);
						emit('autoplay');
					}
				}
				if (swiper.params.cssMode) {
					autoplayStartTime = new Date().getTime();
					requestAnimationFrame(() => {
						run();
					});
				}
			};
			if (delay > 0) {
				clearTimeout(timeout);
				timeout = setTimeout(() => {
					proceed();
				}, delay);
			} else {
				requestAnimationFrame(() => {
					proceed();
				});
			}

			// eslint-disable-next-line
			return delay;
		};
		const start = () => {
			autoplayStartTime = new Date().getTime();
			swiper.autoplay.running = true;
			run();
			emit('autoplayStart');
		};
		const stop = () => {
			swiper.autoplay.running = false;
			clearTimeout(timeout);
			cancelAnimationFrame(raf);
			emit('autoplayStop');
		};
		const pause = (internal, reset) => {
			if (swiper.destroyed || !swiper.autoplay.running) return;
			clearTimeout(timeout);
			if (!internal) {
				pausedByInteraction = true;
			}
			const proceed = () => {
				emit('autoplayPause');
				if (swiper.params.autoplay.waitForTransition) {
					swiper.wrapperEl.addEventListener('transitionend', onTransitionEnd);
				} else {
					resume();
				}
			};
			swiper.autoplay.paused = true;
			if (reset) {
				proceed();
				return;
			}
			const delay = autoplayTimeLeft || swiper.params.autoplay.delay;
			autoplayTimeLeft = delay - (new Date().getTime() - autoplayStartTime);
			if (swiper.isEnd && autoplayTimeLeft < 0 && !swiper.params.loop) return;
			if (autoplayTimeLeft < 0) autoplayTimeLeft = 0;
			proceed();
		};
		const resume = () => {
			if (swiper.isEnd && autoplayTimeLeft < 0 && !swiper.params.loop || swiper.destroyed || !swiper.autoplay.running) return;
			autoplayStartTime = new Date().getTime();
			if (pausedByInteraction) {
				pausedByInteraction = false;
				run(autoplayTimeLeft);
			} else {
				run();
			}
			swiper.autoplay.paused = false;
			emit('autoplayResume');
		};
		const onVisibilityChange = () => {
			if (swiper.destroyed || !swiper.autoplay.running) return;
			const document = getDocument();
			if (document.visibilityState === 'hidden') {
				pausedByInteraction = true;
				pause(true);
			}
			if (document.visibilityState === 'visible') {
				resume();
			}
		};
		const onPointerEnter = e => {
			if (e.pointerType !== 'mouse') return;
			pausedByInteraction = true;
			pausedByPointerEnter = true;
			if (swiper.animating || swiper.autoplay.paused) return;
			pause(true);
		};
		const onPointerLeave = e => {
			if (e.pointerType !== 'mouse') return;
			pausedByPointerEnter = false;
			if (swiper.autoplay.paused) {
				resume();
			}
		};
		const attachMouseEvents = () => {
			if (swiper.params.autoplay.pauseOnMouseEnter) {
				swiper.el.addEventListener('pointerenter', onPointerEnter);
				swiper.el.addEventListener('pointerleave', onPointerLeave);
			}
		};
		const detachMouseEvents = () => {
			if (swiper.el && typeof swiper.el !== 'string') {
				swiper.el.removeEventListener('pointerenter', onPointerEnter);
				swiper.el.removeEventListener('pointerleave', onPointerLeave);
			}
		};
		const attachDocumentEvents = () => {
			const document = getDocument();
			document.addEventListener('visibilitychange', onVisibilityChange);
		};
		const detachDocumentEvents = () => {
			const document = getDocument();
			document.removeEventListener('visibilitychange', onVisibilityChange);
		};
		on('init', () => {
			if (swiper.params.autoplay.enabled) {
				attachMouseEvents();
				attachDocumentEvents();
				start();
			}
		});
		on('destroy', () => {
			detachMouseEvents();
			detachDocumentEvents();
			if (swiper.autoplay.running) {
				stop();
			}
		});
		on('_freeModeStaticRelease', () => {
			if (pausedByTouch || pausedByInteraction) {
				resume();
			}
		});
		on('_freeModeNoMomentumRelease', () => {
			if (!swiper.params.autoplay.disableOnInteraction) {
				pause(true, true);
			} else {
				stop();
			}
		});
		on('beforeTransitionStart', (_s, speed, internal) => {
			if (swiper.destroyed || !swiper.autoplay.running) return;
			if (internal || !swiper.params.autoplay.disableOnInteraction) {
				pause(true, true);
			} else {
				stop();
			}
		});
		on('sliderFirstMove', () => {
			if (swiper.destroyed || !swiper.autoplay.running) return;
			if (swiper.params.autoplay.disableOnInteraction) {
				stop();
				return;
			}
			isTouched = true;
			pausedByTouch = false;
			pausedByInteraction = false;
			touchStartTimeout = setTimeout(() => {
				pausedByInteraction = true;
				pausedByTouch = true;
				pause(true);
			}, 200);
		});
		on('touchEnd', () => {
			if (swiper.destroyed || !swiper.autoplay.running || !isTouched) return;
			clearTimeout(touchStartTimeout);
			clearTimeout(timeout);
			if (swiper.params.autoplay.disableOnInteraction) {
				pausedByTouch = false;
				isTouched = false;
				return;
			}
			if (pausedByTouch && swiper.params.cssMode) resume();
			pausedByTouch = false;
			isTouched = false;
		});
		on('slideChange', () => {
			if (swiper.destroyed || !swiper.autoplay.running) return;
			if (swiper.autoplay.paused) {
				autoplayTimeLeft = getTotalDelay();
				autoplayDelayTotal = getTotalDelay();
			}
		});
		Object.assign(swiper.autoplay, {
			start,
			stop,
			pause,
			resume
		});
	}

	function Thumb({
					   swiper,
					   extendParams,
					   on
				   }) {
		extendParams({
			thumbs: {
				swiper: null,
				multipleActiveThumbs: true,
				autoScrollOffset: 0,
				slideThumbActiveClass: 'swiper-slide-thumb-active',
				thumbsContainerClass: 'swiper-thumbs'
			}
		});
		let initialized = false;
		let swiperCreated = false;
		swiper.thumbs = {
			swiper: null
		};
		function isVirtualEnabled() {
			const thumbsSwiper = swiper.thumbs.swiper;
			if (!thumbsSwiper || thumbsSwiper.destroyed) return false;
			return thumbsSwiper.params.virtual && thumbsSwiper.params.virtual.enabled;
		}
		function onThumbClick() {
			const thumbsSwiper = swiper.thumbs.swiper;
			if (!thumbsSwiper || thumbsSwiper.destroyed) return;
			const clickedIndex = thumbsSwiper.clickedIndex;
			const clickedSlide = thumbsSwiper.clickedSlide;
			if (clickedSlide && clickedSlide.classList.contains(swiper.params.thumbs.slideThumbActiveClass)) return;
			if (typeof clickedIndex === 'undefined' || clickedIndex === null) return;
			let slideToIndex;
			if (thumbsSwiper.params.loop) {
				slideToIndex = parseInt(thumbsSwiper.clickedSlide.getAttribute('data-swiper-slide-index'), 10);
			} else {
				slideToIndex = clickedIndex;
			}
			if (swiper.params.loop) {
				swiper.slideToLoop(slideToIndex);
			} else {
				swiper.slideTo(slideToIndex);
			}
		}
		function init() {
			const {
				thumbs: thumbsParams
			} = swiper.params;
			if (initialized) return false;
			initialized = true;
			const SwiperClass = swiper.constructor;
			if (thumbsParams.swiper instanceof SwiperClass) {
				if (thumbsParams.swiper.destroyed) {
					initialized = false;
					return false;
				}
				swiper.thumbs.swiper = thumbsParams.swiper;
				Object.assign(swiper.thumbs.swiper.originalParams, {
					watchSlidesProgress: true,
					slideToClickedSlide: false
				});
				Object.assign(swiper.thumbs.swiper.params, {
					watchSlidesProgress: true,
					slideToClickedSlide: false
				});
				swiper.thumbs.swiper.update();
			} else if (isObject(thumbsParams.swiper)) {
				const thumbsSwiperParams = Object.assign({}, thumbsParams.swiper);
				Object.assign(thumbsSwiperParams, {
					watchSlidesProgress: true,
					slideToClickedSlide: false
				});
				swiper.thumbs.swiper = new SwiperClass(thumbsSwiperParams);
				swiperCreated = true;
			}
			swiper.thumbs.swiper.el.classList.add(swiper.params.thumbs.thumbsContainerClass);
			swiper.thumbs.swiper.on('tap', onThumbClick);
			if (isVirtualEnabled()) {
				swiper.thumbs.swiper.on('virtualUpdate', () => {
					update(false, {
						autoScroll: false
					});
				});
			}
			return true;
		}
		function update(initial, p) {
			const thumbsSwiper = swiper.thumbs.swiper;
			if (!thumbsSwiper || thumbsSwiper.destroyed) return;

			// Activate thumbs
			let thumbsToActivate = 1;
			const thumbActiveClass = swiper.params.thumbs.slideThumbActiveClass;
			if (swiper.params.slidesPerView > 1 && !swiper.params.centeredSlides) {
				thumbsToActivate = swiper.params.slidesPerView;
			}
			if (!swiper.params.thumbs.multipleActiveThumbs) {
				thumbsToActivate = 1;
			}
			thumbsToActivate = Math.floor(thumbsToActivate);
			thumbsSwiper.slides.forEach(slideEl => slideEl.classList.remove(thumbActiveClass));
			if (thumbsSwiper.params.loop || isVirtualEnabled()) {
				for (let i = 0; i < thumbsToActivate; i += 1) {
					elementChildren(thumbsSwiper.slidesEl, `[data-swiper-slide-index="${swiper.realIndex + i}"]`).forEach(slideEl => {
						slideEl.classList.add(thumbActiveClass);
					});
				}
			} else {
				for (let i = 0; i < thumbsToActivate; i += 1) {
					if (thumbsSwiper.slides[swiper.realIndex + i]) {
						thumbsSwiper.slides[swiper.realIndex + i].classList.add(thumbActiveClass);
					}
				}
			}
			if (p?.autoScroll ?? true) {
				autoScroll(initial ? 0 : undefined);
			}
		}
		function autoScroll(slideSpeed) {
			const thumbsSwiper = swiper.thumbs.swiper;
			if (!thumbsSwiper || thumbsSwiper.destroyed) return;
			const slidesPerView = thumbsSwiper.params.slidesPerView === 'auto' ? thumbsSwiper.slidesPerViewDynamic() : thumbsSwiper.params.slidesPerView;
			const autoScrollOffset = swiper.params.thumbs.autoScrollOffset;
			const useOffset = autoScrollOffset && !thumbsSwiper.params.loop;
			if (swiper.realIndex !== thumbsSwiper.realIndex || useOffset) {
				const currentThumbsIndex = thumbsSwiper.activeIndex;
				let newThumbsIndex;
				let direction;
				if (thumbsSwiper.params.loop) {
					const newThumbsSlide = thumbsSwiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') === `${swiper.realIndex}`);
					newThumbsIndex = thumbsSwiper.slides.indexOf(newThumbsSlide);
					direction = swiper.activeIndex > swiper.previousIndex ? 'next' : 'prev';
				} else {
					newThumbsIndex = swiper.realIndex;
					direction = newThumbsIndex > swiper.previousIndex ? 'next' : 'prev';
				}
				if (useOffset) {
					newThumbsIndex += direction === 'next' ? autoScrollOffset : -1 * autoScrollOffset;
				}
				if (thumbsSwiper.visibleSlidesIndexes && thumbsSwiper.visibleSlidesIndexes.indexOf(newThumbsIndex) < 0) {
					if (thumbsSwiper.params.centeredSlides) {
						if (newThumbsIndex > currentThumbsIndex) {
							newThumbsIndex = newThumbsIndex - Math.floor(slidesPerView / 2) + 1;
						} else {
							newThumbsIndex = newThumbsIndex + Math.floor(slidesPerView / 2) - 1;
						}
					} else if (newThumbsIndex > currentThumbsIndex && thumbsSwiper.params.slidesPerGroup === 1) ;
					thumbsSwiper.slideTo(newThumbsIndex, slideSpeed);
				}
			}
		}
		on('beforeInit', () => {
			const {
				thumbs
			} = swiper.params;
			if (!thumbs || !thumbs.swiper) return;
			if (typeof thumbs.swiper === 'string' || thumbs.swiper instanceof HTMLElement) {
				const document = getDocument();
				const getThumbsElementAndInit = () => {
					const thumbsElement = typeof thumbs.swiper === 'string' ? document.querySelector(thumbs.swiper) : thumbs.swiper;
					if (thumbsElement && thumbsElement.swiper) {
						thumbs.swiper = thumbsElement.swiper;
						init();
						update(true);
					} else if (thumbsElement) {
						const eventName = `${swiper.params.eventsPrefix}init`;
						const onThumbsSwiper = e => {
							thumbs.swiper = e.detail[0];
							thumbsElement.removeEventListener(eventName, onThumbsSwiper);
							init();
							update(true);
							thumbs.swiper.update();
							swiper.update();
						};
						thumbsElement.addEventListener(eventName, onThumbsSwiper);
					}
					return thumbsElement;
				};
				const watchForThumbsToAppear = () => {
					if (swiper.destroyed) return;
					const thumbsElement = getThumbsElementAndInit();
					if (!thumbsElement) {
						requestAnimationFrame(watchForThumbsToAppear);
					}
				};
				requestAnimationFrame(watchForThumbsToAppear);
			} else {
				init();
				update(true);
			}
		});
		on('slideChange update resize observerUpdate', () => {
			update();
		});
		on('setTransition', (_s, duration) => {
			const thumbsSwiper = swiper.thumbs.swiper;
			if (!thumbsSwiper || thumbsSwiper.destroyed) return;
			thumbsSwiper.setTransition(duration);
		});
		on('beforeDestroy', () => {
			const thumbsSwiper = swiper.thumbs.swiper;
			if (!thumbsSwiper || thumbsSwiper.destroyed) return;
			if (swiperCreated) {
				thumbsSwiper.destroy();
			}
		});
		Object.assign(swiper.thumbs, {
			init,
			update
		});
	}

	function effectInit(params) {
		const {
			effect,
			swiper,
			on,
			setTranslate,
			setTransition,
			overwriteParams,
			perspective,
			recreateShadows,
			getEffectParams
		} = params;
		on('beforeInit', () => {
			if (swiper.params.effect !== effect) return;
			swiper.classNames.push(`${swiper.params.containerModifierClass}${effect}`);
			if (perspective && perspective()) {
				swiper.classNames.push(`${swiper.params.containerModifierClass}3d`);
			}
			const overwriteParamsResult = overwriteParams ? overwriteParams() : {};
			Object.assign(swiper.params, overwriteParamsResult);
			Object.assign(swiper.originalParams, overwriteParamsResult);
		});
		on('setTranslate _virtualUpdated', () => {
			if (swiper.params.effect !== effect) return;
			setTranslate();
		});
		on('setTransition', (_s, duration) => {
			if (swiper.params.effect !== effect) return;
			setTransition(duration);
		});
		on('transitionEnd', () => {
			if (swiper.params.effect !== effect) return;
			if (recreateShadows) {
				if (!getEffectParams || !getEffectParams().slideShadows) return;
				// remove shadows
				swiper.slides.forEach(slideEl => {
					slideEl.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(shadowEl => shadowEl.remove());
				});
				// create new one
				recreateShadows();
			}
		});
		let requireUpdateOnVirtual;
		on('virtualUpdate', () => {
			if (swiper.params.effect !== effect) return;
			if (!swiper.slides.length) {
				requireUpdateOnVirtual = true;
			}
			requestAnimationFrame(() => {
				if (requireUpdateOnVirtual && swiper.slides && swiper.slides.length) {
					setTranslate();
					requireUpdateOnVirtual = false;
				}
			});
		});
	}

	function effectTarget(effectParams, slideEl) {
		const transformEl = getSlideTransformEl(slideEl);
		if (transformEl !== slideEl) {
			transformEl.style.backfaceVisibility = 'hidden';
			transformEl.style['-webkit-backface-visibility'] = 'hidden';
		}
		return transformEl;
	}

	function effectVirtualTransitionEnd({
											swiper,
											duration,
											transformElements,
											allSlides
										}) {
		const {
			activeIndex
		} = swiper;
		const getSlide = el => {
			if (!el.parentElement) {
				// assume shadow root
				const slide = swiper.slides.find(slideEl => slideEl.shadowRoot && slideEl.shadowRoot === el.parentNode);
				return slide;
			}
			return el.parentElement;
		};
		if (swiper.params.virtualTranslate && duration !== 0) {
			let eventTriggered = false;
			let transitionEndTarget;
			if (allSlides) {
				transitionEndTarget = transformElements;
			} else {
				transitionEndTarget = transformElements.filter(transformEl => {
					const el = transformEl.classList.contains('swiper-slide-transform') ? getSlide(transformEl) : transformEl;
					return swiper.getSlideIndex(el) === activeIndex;
				});
			}
			transitionEndTarget.forEach(el => {
				elementTransitionEnd(el, () => {
					if (eventTriggered) return;
					if (!swiper || swiper.destroyed) return;
					eventTriggered = true;
					swiper.animating = false;
					const evt = new window.CustomEvent('transitionend', {
						bubbles: true,
						cancelable: true
					});
					swiper.wrapperEl.dispatchEvent(evt);
				});
			});
		}
	}

	function EffectFade({
							swiper,
							extendParams,
							on
						}) {
		extendParams({
			fadeEffect: {
				crossFade: false
			}
		});
		const setTranslate = () => {
			const {
				slides
			} = swiper;
			const params = swiper.params.fadeEffect;
			for (let i = 0; i < slides.length; i += 1) {
				const slideEl = swiper.slides[i];
				const offset = slideEl.swiperSlideOffset;
				let tx = -offset;
				if (!swiper.params.virtualTranslate) tx -= swiper.translate;
				let ty = 0;
				if (!swiper.isHorizontal()) {
					ty = tx;
					tx = 0;
				}
				const slideOpacity = swiper.params.fadeEffect.crossFade ? Math.max(1 - Math.abs(slideEl.progress), 0) : 1 + Math.min(Math.max(slideEl.progress, -1), 0);
				const targetEl = effectTarget(params, slideEl);
				targetEl.style.opacity = slideOpacity;
				targetEl.style.transform = `translate3d(${tx}px, ${ty}px, 0px)`;
			}
		};
		const setTransition = duration => {
			const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl));
			transformElements.forEach(el => {
				el.style.transitionDuration = `${duration}ms`;
			});
			effectVirtualTransitionEnd({
				swiper,
				duration,
				transformElements,
				allSlides: true
			});
		};
		effectInit({
			effect: 'fade',
			swiper,
			on,
			setTranslate,
			setTransition,
			overwriteParams: () => ({
				slidesPerView: 1,
				slidesPerGroup: 1,
				watchSlidesProgress: true,
				spaceBetween: 0,
				virtualTranslate: !swiper.params.cssMode
			})
		});
	}

	/**
	 * Swiper 12.1.3
	 * Most modern mobile touch slider and framework with hardware accelerated transitions
	 * https://swiperjs.com
	 *
	 * Copyright 2014-2026 Vladimir Kharlampidi
	 *
	 * Released under the MIT License
	 *
	 * Released on: March 24, 2026
	 */
	// Swiper Class
	const modules = [Navigation, Pagination, Scrollbar, Parallax, Controller, A11y, Autoplay, Thumb, EffectFade];
	Swiper.use(modules);

	return Swiper;

})();
// Generated by CoffeeScript 1.10.0

/**
@license Sticky-kit v1.1.3 | MIT | Leaf Corcoran 2015 | http://leafo.net
 */

(function() {
  var $, win;

  $ = window.jQuery;

  win = $(window);

  $.fn.stick_in_parent = function(opts) {
    var doc, elm, enable_bottoming, fn, i, inner_scrolling, len, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class;
    if (opts == null) {
      opts = {};
    }
    sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming;
    if (offset_top == null) {
      offset_top = 0;
    }
    if (parent_selector == null) {
      parent_selector = void 0;
    }
    if (inner_scrolling == null) {
      inner_scrolling = true;
    }
    if (sticky_class == null) {
      sticky_class = "is_stuck";
    }
    doc = $(document);
    if (enable_bottoming == null) {
      enable_bottoming = true;
    }
    outer_width = function(el) {
      var _el, computed, w;
      if (window.getComputedStyle) {
        _el = el[0];
        computed = window.getComputedStyle(el[0]);
        w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right"));
        if (computed.getPropertyValue("box-sizing") !== "border-box") {
          w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right"));
        }
        return w;
      } else {
        return el.outerWidth(true);
      }
    };
    fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) {
      var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick;
      if (elm.data("sticky_kit")) {
        return;
      }
      elm.data("sticky_kit", true);
      last_scroll_height = doc.height();
      parent = elm.parent();
      if (parent_selector != null) {
        parent = parent.closest(parent_selector);
      }
      if (!parent.length) {
        throw "failed to find stick parent";
      }
      fixed = false;
      bottomed = false;
      spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("<div />");
      recalc = function() {
        var border_top, padding_top, restore;
        if (detached) {
          return;
        }
        last_scroll_height = doc.height();
        border_top = parseInt(parent.css("border-top-width"), 10);
        padding_top = parseInt(parent.css("padding-top"), 10);
        padding_bottom = parseInt(parent.css("padding-bottom"), 10);
        parent_top = parent.offset().top + border_top + padding_top;
        parent_height = parent.height();
        if (fixed) {
          fixed = false;
          bottomed = false;
          elm.css({
            position: "",
            top: "",
            width: "",
            bottom: ""
          }).removeClass(sticky_class);
          restore = true;
        }
        top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top;
        height = elm.outerHeight(true);
        el_float = elm.css("float");
        if (restore) {
          return tick();
        }
      };
      recalc();
      if (height === parent_height) {
        return;
      }
      last_pos = void 0;
      offset = offset_top;
      recalc_counter = recalc_every;
      tick = function() {
        var css, delta, recalced, scroll, will_bottom, win_height;
        if (detached) {
          return;
        }
        recalced = false;
        if (recalc_counter != null) {
          recalc_counter -= 1;
          if (recalc_counter <= 0) {
            recalc_counter = recalc_every;
            recalc();
            recalced = true;
          }
        }
        if (!recalced && doc.height() !== last_scroll_height) {
          recalc();
          recalced = true;
        }
        scroll = win.scrollTop();
        if (last_pos != null) {
          delta = scroll - last_pos;
        }
        last_pos = scroll;
        if (fixed) {
          if (enable_bottoming) {
            will_bottom = scroll + height + offset > parent_height + parent_top;
            if (bottomed && !will_bottom) {
              bottomed = false;
              elm.css({
                position: "sticky",
                bottom: "",
                top: offset
              }).trigger("sticky_kit:unbottom");
            }
          }
          if (scroll < top) {
            fixed = false;
            offset = offset_top;
            css = {
              position: "",
              width: "",
              top: ""
            };
            elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick");
          }
          if (inner_scrolling) {
            win_height = win.height();
            if (height + offset_top > win_height) {
              if (!bottomed) {
                offset -= delta;
                offset = Math.max(win_height - height, offset);
                offset = Math.min(offset_top, offset);
                if (fixed) {
                  elm.css({
                    top: offset + "px"
                  });
                }
              }
            }
          }
        } else {
          if (scroll > top) {
            fixed = true;
            css = {
              position: "sticky",
              top: offset
            };
            elm.css(css).addClass(sticky_class);
            elm.trigger("sticky_kit:stick");
          }
        }
        if (fixed && enable_bottoming) {
          if (will_bottom == null) {
            will_bottom = scroll + height + offset > parent_height + parent_top;
          }
          if (!bottomed && will_bottom) {
            bottomed = true;
            if (parent.css("position") === "static") {
              parent.css({
                position: "relative"
              });
            }
          }
        }
      };
      recalc_and_tick = function() {
        recalc();
        return tick();
      };
      detach = function() {
        detached = true;
        win.off("touchmove", tick);
        win.off("scroll", tick);
        win.off("resize", recalc_and_tick);
        $(document.body).off("sticky_kit:recalc", recalc_and_tick);
        elm.off("sticky_kit:detach", detach);
        elm.removeData("sticky_kit");
        elm.css({
          position: "",
          bottom: "",
          top: "",
          width: ""
        });
        parent.position("position", "");
        if (fixed) {
          return elm.removeClass(sticky_class);
        }
      };
      win.on("touchmove", tick);
      win.on("scroll", tick);
      win.on("resize", recalc_and_tick);
      $(document.body).on("sticky_kit:recalc", recalc_and_tick);
      elm.on("sticky_kit:detach", detach);
      return setTimeout(tick, 0);
    };
    for (i = 0, len = this.length; i < len; i++) {
      elm = this[i];
      fn($(elm));
    }
    return this;
  };

}).call(this);
/*!
 * Copyright 2012, Chris Wanstrath
 * Released under the MIT License
 * https://github.com/defunkt/jquery-pjax
 */

(function($) {
	// This is an auxiliary woodmart function to replace the outdated jquery method.
	function wdTrim(data) {
		if ( null == data ) {
			return '';
		} else if ( 'string' == typeof data ) {
			return data.trim();
		} else {
			return (data + '').replace( '/^[\\s\uFEFF\xA0]+|[\\s\uFEFF\xA0]+$/g', '' );
		}
	}

	// When called on a container with a selector, fetches the href with
	// ajax into the container or with the data-pjax attribute on the link
	// itself.
	//
	// Tries to make sure the back button and ctrl+click work the way
	// you'd expect.
	//
	// Exported as $.fn.pjax
	//
	// Accepts a jQuery ajax options object that may include these
	// pjax specific options:
	//
	//
	// container - String selector for the element where to place the response body.
	//      push - Whether to pushState the URL. Defaults to true (of course).
	//   replace - Want to use replaceState instead? That's cool.
	//
	// For convenience the second parameter can be either the container or
	// the options object.
	//
	// Returns the jQuery object
	function fnPjax(selector, container, options) {
		options = optionsFor(container, options);
		return this.on('click.pjax', selector, function(event) {
			var opts = options;
			if (!opts.container) {
				opts = $.extend({}, options);
				opts.container = $(this).attr('data-pjax');
			}
			handleClick(event, opts);
		});
	}

	// Public: pjax on click handler
	//
	// Exported as $.pjax.click.
	//
	// event   - "click" jQuery.Event
	// options - pjax options
	//
	// Examples
	//
	//   $(document).on('click', 'a', $.pjax.click)
	//   // is the same as
	//   $(document).pjax('a')
	//
	// Returns nothing.
	function handleClick(event, container, options) {
		options = optionsFor(container, options);

		var link = event.currentTarget;
		var $link = $(link);

		if (link.tagName.toUpperCase() !== 'A') {
			throw '$.fn.pjax or $.pjax.click requires an anchor element';
		}

		// Middle click, cmd click, and ctrl click should open
		// links in a new tab as normal.
		if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
			return;
		}

		// Ignore cross origin links
		if (location.protocol !== link.protocol || location.hostname !== link.hostname) {
			return;
		}

		// Ignore case when a hash is being tacked on the current URL
		if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location)) {
			return;
		}

		// Ignore event with default prevented
		if (event.isDefaultPrevented()) {
			return;
		}

		var defaults = {
			url      : link.href,
			container: $link.attr('data-pjax'),
			target   : link
		};

		var opts = $.extend({}, defaults, options);
		var clickEvent = $.Event('pjax:click');
		$link.trigger(clickEvent, [opts]);

		if (!clickEvent.isDefaultPrevented()) {
			pjax(opts);
			event.preventDefault();
			$link.trigger('pjax:clicked', [opts]);
		}
	}

	// Public: pjax on form submit handler
	//
	// Exported as $.pjax.submit
	//
	// event   - "click" jQuery.Event
	// options - pjax options
	//
	// Examples
	//
	//  $(document).on('submit', 'form', function(event) {
	//    $.pjax.submit(event, '[data-pjax-container]')
	//  })
	//
	// Returns nothing.
	function handleSubmit(event, container, options) {
		options = optionsFor(container, options);

		var form = event.currentTarget;
		var $form = $(form);

		if (form.tagName.toUpperCase() !== 'FORM') {
			throw '$.pjax.submit requires a form element';
		}

		var defaults = {
			type     : ($form.attr('method') || 'GET').toUpperCase(),
			url      : $form.attr('action'),
			container: $form.attr('data-pjax'),
			target   : form
		};

		if (defaults.type !== 'GET' && window.FormData !== undefined) {
			defaults.data = new FormData(form);
			defaults.processData = false;
			defaults.contentType = false;
		} else {
			// Can't handle file uploads, exit
			if ($form.find(':file').length) {
				return;
			}

			// Fallback to manually serializing the fields
			defaults.data = $form.serializeArray();
		}

		pjax($.extend({}, defaults, options));

		event.preventDefault();
	}

	// Loads a URL with ajax, puts the response body inside a container,
	// then pushState()'s the loaded URL.
	//
	// Works just like $.ajax in that it accepts a jQuery ajax
	// settings object (with keys like url, type, data, etc).
	//
	// Accepts these extra keys:
	//
	// container - String selector for where to stick the response body.
	//      push - Whether to pushState the URL. Defaults to true (of course).
	//   replace - Want to use replaceState instead? That's cool.
	//
	// Use it just like $.ajax:
	//
	//   var xhr = $.pjax({ url: this.href, container: '#main' })
	//   console.log( xhr.readyState )
	//
	// Returns whatever $.ajax returns.
	function pjax(options) {
		options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options);

		if (typeof options.url === 'function') {
			options.url = options.url();
		}

		var hash = parseURL(options.url).hash;

		var containerType = typeof options.container;
		if (containerType !== 'string') {
			throw 'expected string value for \'container\' option; got ' + containerType;
		}
		var context = options.context = $(options.container);
		if (!context.length) {
			throw 'the container selector \'' + options.container + '\' did not match anything';
		}

		// We want the browser to maintain two separate internal caches: one
		// for pjax'd partial page loads and one for normal page loads.
		// Without adding this secret parameter, some browsers will often
		// confuse the two.
		if (!options.data) {
			options.data = {};
		}
		if (Array.isArray(options.data)) {
			options.data.push({
				name : '_pjax',
				value: options.container
			});
		} else {
			options.data._pjax = options.container;
		}

		function fire(type, args, props) {
			if (!props) {
				props = {};
			}
			props.relatedTarget = options.target;
			var event = $.Event(type, props);
			context.trigger(event, args);
			return !event.isDefaultPrevented();
		}

		var timeoutTimer;

		options.beforeSend = function(xhr, settings) {
			// No timeout for non-GET requests
			// Its not safe to request the resource again with a fallback method.
			if (settings.type !== 'GET') {
				settings.timeout = 0;
			}

			xhr.setRequestHeader('X-PJAX', 'true');
			xhr.setRequestHeader('X-PJAX-Container', options.container);

			if (!fire('pjax:beforeSend', [
				xhr,
				settings
			])) {
				return false;
			}

			if (settings.timeout > 0) {
				timeoutTimer = setTimeout(function() {
					if (fire('pjax:timeout', [
						xhr,
						options
					])) {
						xhr.abort('timeout');
					}
				}, settings.timeout);

				// Clear timeout setting so jquerys internal timeout isn't invoked
				settings.timeout = 0;
			}

			var url = parseURL(settings.url);
			if (hash) {
				url.hash = hash;
			}
			options.requestUrl = stripInternalParams(url);
		};

		options.complete = function(xhr, textStatus) {
			if (timeoutTimer) {
				clearTimeout(timeoutTimer);
			}

			fire('pjax:complete', [
				xhr,
				textStatus,
				options
			]);

			fire('pjax:end', [
				xhr,
				options
			]);
		};

		options.error = function(xhr, textStatus, errorThrown) {
			var container = extractContainer('', xhr, options);

			var allowed = fire('pjax:error', [
				xhr,
				textStatus,
				errorThrown,
				options
			]);
			if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
				locationReplace(container.url);
			}
		};

		options.success = function(data, status, xhr) {
			var previousState = pjax.state;

			// If $.pjax.defaults.version is a function, invoke it first.
			// Otherwise it can be a static string.
			var currentVersion = typeof $.pjax.defaults.version === 'function' ?
				$.pjax.defaults.version() :
				$.pjax.defaults.version;

			var latestVersion = xhr.getResponseHeader('X-PJAX-Version');

			var container = extractContainer(data, xhr, options);

			var url = parseURL(container.url);
			if (hash) {
				url.hash = hash;
				container.url = url.href;
			}

			// If there is a layout version mismatch, hard load the new url
			if (currentVersion && latestVersion && currentVersion !== latestVersion) {
				locationReplace(container.url);
				return;
			}

			// If the new response is missing a body, hard load the page
			if (!container.contents) {
				locationReplace(container.url);
				return;
			}

			pjax.state = {
				id       : options.id || uniqueId(),
				url      : container.url,
				title    : container.title,
				container: options.container,
				fragment : options.fragment,
				timeout  : options.timeout
			};

			if (options.push || options.replace) {
				window.history.replaceState(pjax.state, container.title, container.url);
			}

			// Only blur the focus if the focused element is within the container.
			var blurFocus = $.contains(context, document.activeElement);

			// Clear out any focused controls before inserting new page contents.
			if (blurFocus) {
				try {
					document.activeElement.blur();
				}
				catch (e) { /* ignore */
				}
			}

			if (container.title) {
				document.title = container.title;
			}

			fire('pjax:beforeReplace', [
				container.contents,
				options
			], {
				state        : pjax.state,
				previousState: previousState
			});

			if ('function' === typeof options.renderCallback) {
				options.renderCallback(context, container.contents, afterRender);
			} else {
				context.html(container.contents);
				afterRender();
			}

			function afterRender() {
				// FF bug: Won't autofocus fields that are inserted via JS.
				// This behavior is incorrect. So if theres no current focus, autofocus
				// the last field.
				//
				// http://www.w3.org/html/wg/drafts/html/master/forms.html
				var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0];
				if (autofocusEl && document.activeElement !== autofocusEl) {
					autofocusEl.trigger('focus');
				}

				executeScriptTags(container.scripts);

				var scrollTo = options.scrollTo;

				// Ensure browser scrolls to the element referenced by the URL anchor
				if (hash) {
					var name = decodeURIComponent(hash.slice(1));
					var target = document.getElementById(name) || document.getElementsByName(name)[0];
					if (target) {
						scrollTo = $(target).offset().top;
					}
				}

				if (typeof scrollTo == 'number') {
					$(window).scrollTop(scrollTo);
				}

				fire('pjax:success', [
					data,
					status,
					xhr,
					options
				]);
			}
		};

		// Initialize pjax.state for the initial page load. Assume we're
		// using the container and options of the link we're loading for the
		// back button to the initial page. This ensures good back button
		// behavior.
		if (!pjax.state) {
			pjax.state = {
				id       : uniqueId(),
				url      : window.location.href,
				title    : document.title,
				container: options.container,
				fragment : options.fragment,
				timeout  : options.timeout
			};
			window.history.replaceState(pjax.state, document.title);
		}

		// Cancel the current request if we're already pjaxing
		abortXHR(pjax.xhr);

		pjax.options = options;
		var xhr = pjax.xhr = $.ajax(options);

		if (xhr.readyState > 0) {
			if (options.push && !options.replace) {
				// Cache current container element before replacing it
				cachePush(pjax.state.id, [
					options.container,
					cloneContents(context)
				]);

				window.history.pushState(null, '', options.requestUrl);
			}

			fire('pjax:start', [
				xhr,
				options
			]);
			fire('pjax:send', [
				xhr,
				options
			]);
		}

		return pjax.xhr;
	}

	// Public: Reload current page with pjax.
	//
	// Returns whatever $.pjax returns.
	function pjaxReload(container, options) {
		var defaults = {
			url     : window.location.href,
			push    : false,
			replace : true,
			scrollTo: false
		};

		return pjax($.extend(defaults, optionsFor(container, options)));
	}

	// Internal: Hard replace current state with url.
	//
	// Work for around WebKit
	//   https://bugs.webkit.org/show_bug.cgi?id=93506
	//
	// Returns nothing.
	function locationReplace(url) {
		window.history.replaceState(null, '', pjax.state.url);
		window.location.replace(url);
	}

	var initialPop = true;
	var initialURL = window.location.href;
	var initialState = window.history.state;

	// Initialize $.pjax.state if possible
	// Happens when reloading a page and coming forward from a different
	// session history.
	if (initialState && initialState.container) {
		pjax.state = initialState;
	}

	// Non-webkit browsers don't fire an initial popstate event
	if ('state' in window.history) {
		initialPop = false;
	}

	// popstate handler takes care of the back and forward buttons
	//
	// You probably shouldn't use pjax on pages with other pushState
	// stuff yet.
	function onPjaxPopstate(event) {

		// Hitting back or forward should override any pending PJAX request.
		if (!initialPop) {
			abortXHR(pjax.xhr);
		}

		var previousState = pjax.state;
		var state = event.state;
		var direction;

		if (state && state.container) {
			// When coming forward from a separate history session, will get an
			// initial pop with a state we are already at. Skip reloading the current
			// page.
			if (initialPop && initialURL == state.url) {
				return;
			}

			if (previousState) {
				// If popping back to the same state, just skip.
				// Could be clicking back from hashchange rather than a pushState.
				if (previousState.id === state.id) {
					return;
				}

				// Since state IDs always increase, we can deduce the navigation direction
				direction = previousState.id < state.id ? 'forward' : 'back';
			}

			var cache = cacheMapping[state.id] || [];
			var containerSelector = cache[0] || state.container;
			var container = $(containerSelector), contents = cache[1];

			if (container.length) {
				if (previousState) {
					// Cache current container before replacement and inform the
					// cache which direction the history shifted.
					cachePop(direction, previousState.id, [
						containerSelector,
						cloneContents(container)
					]);
				}

				var popstateEvent = $.Event('pjax:popstate', {
					state    : state,
					direction: direction
				});
				container.trigger(popstateEvent);

				var options = {
					id       : state.id,
					url      : state.url,
					container: containerSelector,
					push     : false,
					fragment : state.fragment,
					timeout  : state.timeout,
					scrollTo : false
				};

				if (contents) {
					container.trigger('pjax:start', [
						null,
						options
					]);

					pjax.state = state;
					if (state.title) {
						document.title = state.title;
					}
					var beforeReplaceEvent = $.Event('pjax:beforeReplace', {
						state        : state,
						previousState: previousState
					});
					container.trigger(beforeReplaceEvent, [
						contents,
						options
					]);
					container.html(contents);

					container.trigger('pjax:end', [
						null,
						options
					]);
				} else {
					pjax(options);
				}

				// Force reflow/relayout before the browser tries to restore the
				// scroll position.
				container[0].offsetHeight; // eslint-disable-line no-unused-expressions
			} else {
				locationReplace(location.href);
			}
		}
		initialPop = false;
	}

	// Fallback version of main pjax function for browsers that don't
	// support pushState.
	//
	// Returns nothing since it retriggers a hard form submission.
	function fallbackPjax(options) {
		var url    = typeof options.url === 'function' ? options.url() : options.url,
		    method = options.type ? options.type.toUpperCase() : 'GET';

		var form = $('<form>', {
			method: method === 'GET' ? 'GET' : 'POST',
			action: url,
			style : 'display:none'
		});

		if (method !== 'GET' && method !== 'POST') {
			form.append($('<input>', {
				type : 'hidden',
				name : '_method',
				value: method.toLowerCase()
			}));
		}

		var data = options.data;
		if (typeof data === 'string') {
			$.each(data.split('&'), function(index, value) {
				var pair = value.split('=');
				form.append($('<input>', {
					type : 'hidden',
					name : pair[0],
					value: pair[1]
				}));
			});
		} else if (Array.isArray(data)) {
			$.each(data, function(index, value) {
				form.append($('<input>', {
					type : 'hidden',
					name : value.name,
					value: value.value
				}));
			});
		} else if (typeof data === 'object') {
			var key;
			for (key in data) {
				form.append($('<input>', {
					type : 'hidden',
					name : key,
					value: data[key]
				}));
			}
		}

		$(document.body).append(form);
		form.submit();
	}

	// Internal: Abort an XmlHttpRequest if it hasn't been completed,
	// also removing its event handlers.
	function abortXHR(xhr) {
		if (xhr && xhr.readyState < 4) {
			xhr.onreadystatechange = $.noop;
			xhr.abort();
		}
	}

	// Internal: Generate unique id for state object.
	//
	// Use a timestamp instead of a counter since ids should still be
	// unique across page loads.
	//
	// Returns Number.
	function uniqueId() {
		return (new Date).getTime();
	}

	function cloneContents(container) {
		var cloned = container.clone();
		// Unmark script tags as already being eval'd so they can get executed again
		// when restored from cache. HAXX: Uses jQuery internal method.
		cloned.find('script').each(function() {
			if (!this.src) {
				jQuery._data(this, 'globalEval', false);
			}
		});
		return cloned.contents();
	}

	// Internal: Strip internal query params from parsed URL.
	//
	// Returns sanitized url.href String.
	function stripInternalParams(url) {
		url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '');
		return url.href.replace(/\?($|#)/, '$1');
	}

	// Internal: Parse URL components and returns a Locationish object.
	//
	// url - String URL
	//
	// Returns HTMLAnchorElement that acts like Location.
	function parseURL(url) {
		var a = document.createElement('a');
		a.href = url;
		return a;
	}

	// Internal: Return the `href` component of given URL object with the hash
	// portion removed.
	//
	// location - Location or HTMLAnchorElement
	//
	// Returns String
	function stripHash(location) {
		return location.href.replace(/#.*/, '');
	}

	// Internal: Build options Object for arguments.
	//
	// For convenience the first parameter can be either the container or
	// the options object.
	//
	// Examples
	//
	//   optionsFor('#container')
	//   // => {container: '#container'}
	//
	//   optionsFor('#container', {push: true})
	//   // => {container: '#container', push: true}
	//
	//   optionsFor({container: '#container', push: true})
	//   // => {container: '#container', push: true}
	//
	// Returns options Object.
	function optionsFor(container, options) {
		if (container && options) {
			options = $.extend({}, options);
			options.container = container;
			return options;
		} else if ($.isPlainObject(container)) {
			return container;
		} else {
			return {container: container};
		}
	}

	// Internal: Filter and find all elements matching the selector.
	//
	// Where $.fn.find only matches descendants, findAll will test all the
	// top level elements in the jQuery object as well.
	//
	// elems    - jQuery object of Elements
	// selector - String selector to match
	//
	// Returns a jQuery object.
	function findAll(elems, selector) {
		return elems.filter(selector).add(elems.find(selector));
	}

	function parseHTML(html) {
		return $.parseHTML(html, document, true);
	}

	// Internal: Extracts container and metadata from response.
	//
	// 1. Extracts X-PJAX-URL header if set
	// 2. Extracts inline <title> tags
	// 3. Builds response Element and extracts fragment if set
	//
	// data    - String response data
	// xhr     - XHR response
	// options - pjax options Object
	//
	// Returns an Object with url, title, and contents keys.
	function extractContainer(data, xhr, options) {
		var obj = {}, fullDocument = /<html/i.test(data);

		// Prefer X-PJAX-URL header if it was set, otherwise fallback to
		// using the original requested url.
		var serverUrl = xhr.getResponseHeader('X-PJAX-URL');
		obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl;

		var $head, $body;
		// Attempt to parse response html into elements
		if (fullDocument) {
			$body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]));
			var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i);
			$head = head != null ? $(parseHTML(head[0])) : $body;
		} else {
			$head = $body = $(parseHTML(data));
		}

		// If response data is empty, return fast
		if ($body.length === 0) {
			return obj;
		}

		// If there's a <title> tag in the header, use it as
		// the page's title.
		obj.title = findAll($head, 'title').last().text();

		if (options.fragment) {
			var $fragment = $body;
			// If they specified a fragment, look for it in the response
			// and pull it out.
			if (options.fragment !== 'body') {
				$fragment = findAll($fragment, options.fragment).first();
			}

			if ($fragment.length) {
				obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents();

				// If there's no title, look for data-title and title attributes
				// on the fragment
				if (!obj.title) {
					obj.title = $fragment.attr('title') || $fragment.data('title');
				}
			}

		} else if (!fullDocument) {
			obj.contents = $body;
		}

		// Clean up any <title> tags
		if (obj.contents) {
			// Remove any parent title elements
			obj.contents = obj.contents.not(function() { return $(this).is('title'); });

			// Then scrub any titles from their descendants
			obj.contents.find('title').remove();

			// Gather all script[src] elements
			obj.scripts = findAll(obj.contents, 'script[src]').remove();
			obj.contents = obj.contents.not(obj.scripts);
		}

		// Trim any whitespace off the title
		if (obj.title) {
			obj.title = wdTrim(obj.title);
		}

		return obj;
	}

	// Load an execute scripts using standard script request.
	//
	// Avoids jQuery's traditional $.getScript which does a XHR request and
	// globalEval.
	//
	// scripts - jQuery object of script Elements
	//
	// Returns nothing.
	function executeScriptTags(scripts) {
		if (!scripts) {
			return;
		}

		var existingScripts = $('script[src]');

		scripts.each(function() {
			var src = this.src;
			var matchedScripts = existingScripts.filter(function() {
				return this.src === src;
			});
			if (matchedScripts.length) {
				return;
			}

			var script = document.createElement('script');
			var type = $(this).attr('type');
			if (type) {
				script.type = type;
			}
			script.src = $(this).attr('src');
			document.head.appendChild(script);
		});
	}

	// Internal: History DOM caching class.
	var cacheMapping = {};
	var cacheForwardStack = [];
	var cacheBackStack = [];

	// Push previous state id and container contents into the history
	// cache. Should be called in conjunction with `pushState` to save the
	// previous container contents.
	//
	// id    - State ID Number
	// value - DOM Element to cache
	//
	// Returns nothing.
	function cachePush(id, value) {
		cacheMapping[id] = value;
		cacheBackStack.push(id);

		// Remove all entries in forward history stack after pushing a new page.
		trimCacheStack(cacheForwardStack, 0);

		// Trim back history stack to max cache length.
		trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength);
	}

	// Shifts cache from directional history cache. Should be
	// called on `popstate` with the previous state id and container
	// contents.
	//
	// direction - "forward" or "back" String
	// id        - State ID Number
	// value     - DOM Element to cache
	//
	// Returns nothing.
	function cachePop(direction, id, value) {
		var pushStack, popStack;
		cacheMapping[id] = value;

		if (direction === 'forward') {
			pushStack = cacheBackStack;
			popStack = cacheForwardStack;
		} else {
			pushStack = cacheForwardStack;
			popStack = cacheBackStack;
		}

		pushStack.push(id);
		id = popStack.pop();
		if (id) {
			delete cacheMapping[id];
		}

		// Trim whichever stack we just pushed to to max cache length.
		trimCacheStack(pushStack, pjax.defaults.maxCacheLength);
	}

	// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no
	// longer than the specified length, deleting cached DOM elements as necessary.
	//
	// stack  - Array of state IDs
	// length - Maximum length to trim to
	//
	// Returns nothing.
	function trimCacheStack(stack, length) {
		while (stack.length > length) {
			delete cacheMapping[stack.shift()];
		}
	}

	// Public: Find version identifier for the initial page load.
	//
	// Returns String version or undefined.
	function findVersion() {
		return $('meta').filter(function() {
			var name = $(this).attr('http-equiv');
			return name && name.toUpperCase() === 'X-PJAX-VERSION';
		}).attr('content');
	}

	// Install pjax functions on $.pjax to enable pushState behavior.
	//
	// Does nothing if already enabled.
	//
	// Examples
	//
	//     $.pjax.enable()
	//
	// Returns nothing.
	function enable() {
		$.fn.pjax = fnPjax;
		$.pjax = pjax;
		$.pjax.enable = $.noop;
		$.pjax.disable = disable;
		$.pjax.click = handleClick;
		$.pjax.submit = handleSubmit;
		$.pjax.reload = pjaxReload;
		$.pjax.defaults = {
			timeout       : 650,
			push          : true,
			replace       : false,
			type          : 'GET',
			dataType      : 'html',
			scrollTo      : 0,
			maxCacheLength: 20,
			version       : findVersion
		};
		$(window).on('popstate.pjax', onPjaxPopstate);
	}

	// Disable pushState behavior.
	//
	// This is the case when a browser doesn't support pushState. It is
	// sometimes useful to disable pushState for debugging on a modern
	// browser.
	//
	// Examples
	//
	//     $.pjax.disable()
	//
	// Returns nothing.
	function disable() {
		$.fn.pjax = function() { return this; };
		$.pjax = fallbackPjax;
		$.pjax.enable = enable;
		$.pjax.disable = $.noop;
		$.pjax.click = $.noop;
		$.pjax.submit = $.noop;
		$.pjax.reload = function() { window.location.reload(); };

		$(window).off('popstate.pjax', onPjaxPopstate);
	}

	// Add the state property to jQuery's event object so we can use it in
	// $(window).bind('popstate')
	if (!('state' in $.Event.prototype)) {
		$.event.addProp('state');
	}

	// Is pjax supported by this browser?
	$.support.pjax =
		window.history && window.history.pushState && window.history.replaceState &&
		// pushState isn't reliable on iOS until 5.
		!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)

	if ($.support.pjax) {
		enable()
	} else {
		disable()
	}

})(jQuery);
/*! PhotoSwipe - v4.1.0 - 2015-07-23
* http://photoswipe.com
* Copyright (c) 2015 Dmitry Semenov; */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.PhotoSwipe = factory();
    }
})(this, function () {

    'use strict';
    var PhotoSwipe = function (template, UiClass, items, options) {

        /*>>framework-bridge*/
        /**
         *
         * Set of generic functions used by gallery.
         * 
         * You're free to modify anything here as long as functionality is kept.
         * 
         */
        var framework = {
            features: null,
            bind: function (target, type, listener, unbind) {
                var methodName = (unbind ? 'remove' : 'add') + 'EventListener';
                type = type.split(' ');
                for (var i = 0; i < type.length; i++) {
                    if (type[i]) {
                        target[methodName](type[i], listener, false);
                    }
                }
            },
            isArray: function (obj) {
                return (obj instanceof Array);
            },
            createEl: function (classes, tag) {
                var el = document.createElement(tag || 'div');
                if (classes) {
                    el.className = classes;
                }
                return el;
            },
            getScrollY: function () {
                var yOffset = window.pageYOffset;
                return yOffset !== undefined ? yOffset : document.documentElement.scrollTop;
            },
            unbind: function (target, type, listener) {
                framework.bind(target, type, listener, true);
            },
            removeClass: function (el, className) {
                var reg = new RegExp('(\\s|^)' + className + '(\\s|$)');
                el.className = el.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
            },
            addClass: function (el, className) {
                if (!framework.hasClass(el, className)) {
                    el.className += (el.className ? ' ' : '') + className;
                }
            },
            hasClass: function (el, className) {
                return el.className && new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
            },
            getChildByClass: function (parentEl, childClassName) {
                var node = parentEl.firstChild;
                while (node) {
                    if (framework.hasClass(node, childClassName)) {
                        return node;
                    }
                    node = node.nextSibling;
                }
            },
            arraySearch: function (array, value, key) {
                var i = array.length;
                while (i--) {
                    if (array[i][key] === value) {
                        return i;
                    }
                }
                return -1;
            },
            extend: function (o1, o2, preventOverwrite) {
                for (var prop in o2) {
                    if (o2.hasOwnProperty(prop)) {
                        if (preventOverwrite && o1.hasOwnProperty(prop)) {
                            continue;
                        }
                        o1[prop] = o2[prop];
                    }
                }
            },
            easing: {
                sine: {
                    out: function (k) {
                        return Math.sin(k * (Math.PI / 2));
                    },
                    inOut: function (k) {
                        return - (Math.cos(Math.PI * k) - 1) / 2;
                    }
                },
                cubic: {
                    out: function (k) {
                        return --k * k * k + 1;
                    }
                }
                /*
                    elastic: {
                        out: function ( k ) {
        
                            var s, a = 0.1, p = 0.4;
                            if ( k === 0 ) return 0;
                            if ( k === 1 ) return 1;
                            if ( !a || a < 1 ) { a = 1; s = p / 4; }
                            else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
                            return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
        
                        },
                    },
                    back: {
                        out: function ( k ) {
                            var s = 1.70158;
                            return --k * k * ( ( s + 1 ) * k + s ) + 1;
                        }
                    }
                */
            },

            /**
             * 
             * @return {object}
             * 
             * {
             *  raf : request animation frame function
             *  caf : cancel animation frame function
             *  transfrom : transform property key (with vendor), or null if not supported
             *  oldIE : IE8 or below
             * }
             * 
             */
            detectFeatures: function () {
                if (framework.features) {
                    return framework.features;
                }
                var helperEl = framework.createEl(),
                    helperStyle = helperEl.style,
                    vendor = '',
                    features = {};

                // IE8 and below
                features.oldIE = document.all && !document.addEventListener;

                features.touch = 'ontouchstart' in window;

                if (window.requestAnimationFrame) {
                    features.raf = window.requestAnimationFrame;
                    features.caf = window.cancelAnimationFrame;
                }

                features.pointerEvent = navigator.pointerEnabled || navigator.msPointerEnabled;

                // fix false-positive detection of old Android in new IE
                // (IE11 ua string contains "Android 4.0")

                if (!features.pointerEvent) {

                    var ua = navigator.userAgent;

                    // Detect if device is iPhone or iPod and if it's older than iOS 8
                    // http://stackoverflow.com/a/14223920
                    // 
                    // This detection is made because of buggy top/bottom toolbars
                    // that don't trigger window.resize event.
                    // For more info refer to _isFixedPosition variable in core.js

                    if (/iP(hone|od)/.test(navigator.platform)) {
                        var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
                        if (v && v.length > 0) {
                            v = parseInt(v[1], 10);
                            if (v >= 1 && v < 8) {
                                features.isOldIOSPhone = true;
                            }
                        }
                    }

                    // Detect old Android (before KitKat)
                    // due to bugs related to position:fixed
                    // http://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript

                    var match = ua.match(/Android\s([0-9\.]*)/);
                    var androidversion = match ? match[1] : 0;
                    androidversion = parseFloat(androidversion);
                    if (androidversion >= 1) {
                        if (androidversion < 4.4) {
                            features.isOldAndroid = true; // for fixed position bug & performance
                        }
                        features.androidVersion = androidversion; // for touchend bug
                    }
                    features.isMobileOpera = /opera mini|opera mobi/i.test(ua);

                    // p.s. yes, yes, UA sniffing is bad, propose your solution for above bugs.
                }

                var styleChecks = ['transform', 'perspective', 'animationName'],
                    vendors = ['', 'webkit', 'Moz', 'ms', 'O'],
                    styleCheckItem,
                    styleName;

                for (var i = 0; i < 4; i++) {
                    vendor = vendors[i];

                    for (var a = 0; a < 3; a++) {
                        styleCheckItem = styleChecks[a];

                        // uppercase first letter of property name, if vendor is present
                        styleName = vendor + (vendor ?
                            styleCheckItem.charAt(0).toUpperCase() + styleCheckItem.slice(1) :
                            styleCheckItem);

                        if (!features[styleCheckItem] && styleName in helperStyle) {
                            features[styleCheckItem] = styleName;
                        }
                    }

                    if (vendor && !features.raf) {
                        vendor = vendor.toLowerCase();
                        features.raf = window[vendor + 'RequestAnimationFrame'];
                        if (features.raf) {
                            features.caf = window[vendor + 'CancelAnimationFrame'] ||
                                window[vendor + 'CancelRequestAnimationFrame'];
                        }
                    }
                }

                if (!features.raf) {
                    var lastTime = 0;
                    features.raf = function (fn) {
                        var currTime = new Date().getTime();
                        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                        var id = window.setTimeout(function () { fn(currTime + timeToCall); }, timeToCall);
                        lastTime = currTime + timeToCall;
                        return id;
                    };
                    features.caf = function (id) { clearTimeout(id); };
                }

                // Detect SVG support
                features.svg = !!document.createElementNS &&
                    !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect;

                framework.features = features;

                return features;
            }
        };

        framework.detectFeatures();

        // Override addEventListener for old versions of IE
        if (framework.features.oldIE) {

            framework.bind = function (target, type, listener, unbind) {

                type = type.split(' ');

                var methodName = (unbind ? 'detach' : 'attach') + 'Event',
                    evName,
                    _handleEv = function () {
                        listener.handleEvent.call(listener);
                    };

                for (var i = 0; i < type.length; i++) {
                    evName = type[i];
                    if (evName) {

                        if (typeof listener === 'object' && listener.handleEvent) {
                            if (!unbind) {
                                listener['oldIE' + evName] = _handleEv;
                            } else {
                                if (!listener['oldIE' + evName]) {
                                    return false;
                                }
                            }

                            target[methodName]('on' + evName, listener['oldIE' + evName]);
                        } else {
                            target[methodName]('on' + evName, listener);
                        }

                    }
                }
            };

        }

        /*>>framework-bridge*/

        /*>>core*/
        //function(template, UiClass, items, options)

        var self = this;

        /**
         * Static vars, don't change unless you know what you're doing.
         */
        var DOUBLE_TAP_RADIUS = 25,
            NUM_HOLDERS = 3;

        /**
         * Options
         */
        var _options = {
            allowPanToNext: true,
            spacing: 0.12,
            bgOpacity: 1,
            mouseUsed: false,
            loop: true,
            pinchToClose: true,
            closeOnScroll: true,
            closeOnVerticalDrag: true,
            verticalDragRange: 0.75,
            hideAnimationDuration: 333,
            showAnimationDuration: 333,
            showHideOpacity: false,
            focus: true,
            escKey: true,
            arrowKeys: true,
            mainScrollEndFriction: 0.35,
            panEndFriction: 0.35,
            isClickableElement: function (el) {
                return el.tagName === 'A';
            },
            getDoubleTapZoom: function (isMouseClick, item) {
                if (isMouseClick) {
                    return 1;
                } else {
                    return item.initialZoomLevel < 0.7 ? 1 : 1.33;
                }
            },
            maxSpreadZoom: 1.33,
            modal: true,

            // not fully implemented yet
            scaleMode: 'fit' // TODO
        };
        framework.extend(_options, options);


        /**
         * Private helper variables & functions
         */

        var _getEmptyPoint = function () {
            return { x: 0, y: 0 };
        };

        var _isOpen,
            _isDestroying,
            _closedByScroll,
            _currentItemIndex,
            _containerStyle,
            _containerShiftIndex,
            _currPanDist = _getEmptyPoint(),
            _startPanOffset = _getEmptyPoint(),
            _panOffset = _getEmptyPoint(),
            _upMoveEvents, // drag move, drag end & drag cancel events array
            _downEvents, // drag start events array
            _globalEventHandlers,
            _viewportSize = {},
            _currZoomLevel,
            _startZoomLevel,
            _translatePrefix,
            _translateSufix,
            _updateSizeInterval,
            _itemsNeedUpdate,
            _currPositionIndex = 0,
            _offset = {},
            _slideSize = _getEmptyPoint(), // size of slide area, including spacing
            _itemHolders,
            _prevItemIndex,
            _indexDiff = 0, // difference of indexes since last content update
            _dragStartEvent,
            _dragMoveEvent,
            _dragEndEvent,
            _dragCancelEvent,
            _transformKey,
            _pointerEventEnabled,
            _isFixedPosition = true,
            _likelyTouchDevice,
            _modules = [],
            _requestAF,
            _cancelAF,
            _initalClassName,
            _initalWindowScrollY,
            _oldIE,
            _currentWindowScrollY,
            _features,
            _windowVisibleSize = {},
            _renderMaxResolution = false,

            // Registers PhotoSWipe module (Controller ...)
            _registerModule = function (name, module) {
                framework.extend(self, module.publicMethods);
                _modules.push(name);
            },

            _getLoopedId = function (index) {
                var numSlides = _getNumItems();
                if (index > numSlides - 1) {
                    return index - numSlides;
                } else if (index < 0) {
                    return numSlides + index;
                }
                return index;
            },

            // Micro bind/trigger
            _listeners = {},
            _listen = function (name, fn) {
                if (!_listeners[name]) {
                    _listeners[name] = [];
                }
                return _listeners[name].push(fn);
            },
            _shout = function (name) {
                var listeners = _listeners[name];

                if (listeners) {
                    var args = Array.prototype.slice.call(arguments);
                    args.shift();

                    for (var i = 0; i < listeners.length; i++) {
                        listeners[i].apply(self, args);
                    }
                }
            },

            _getCurrentTime = function () {
                return new Date().getTime();
            },
            _applyBgOpacity = function (opacity) {
                _bgOpacity = opacity;
                self.bg.style.opacity = opacity * _options.bgOpacity;
            },

            _applyZoomTransform = function (styleObj, x, y, zoom, item) {
                if (!_renderMaxResolution || (item && item !== self.currItem)) {
                    zoom = zoom / (item ? item.fitRatio : self.currItem.fitRatio);
                }

                styleObj[_transformKey] = _translatePrefix + x + 'px, ' + y + 'px' + _translateSufix + ' scale(' + zoom + ')';
            },
            _applyCurrentZoomPan = function (allowRenderResolution) {
                if (_currZoomElementStyle) {

                    if (allowRenderResolution) {
                        if (_currZoomLevel > self.currItem.fitRatio) {
                            if (!_renderMaxResolution) {
                                _setImageSize(self.currItem, false, true);
                                _renderMaxResolution = true;
                            }
                        } else {
                            if (_renderMaxResolution) {
                                _setImageSize(self.currItem);
                                _renderMaxResolution = false;
                            }
                        }
                    }


                    _applyZoomTransform(_currZoomElementStyle, _panOffset.x, _panOffset.y, _currZoomLevel);
                }
            },
            _applyZoomPanToItem = function (item) {
                if (item.container) {

                    _applyZoomTransform(item.container.style,
                        item.initialPosition.x,
                        item.initialPosition.y,
                        item.initialZoomLevel,
                        item);
                }
            },
            _setTranslateX = function (x, elStyle) {
                elStyle[_transformKey] = _translatePrefix + x + 'px, 0px' + _translateSufix;
            },
            _moveMainScroll = function (x, dragging) {

                if (!_options.loop && dragging) {
                    var newSlideIndexOffset = _currentItemIndex + (_slideSize.x * _currPositionIndex - x) / _slideSize.x,
                        delta = Math.round(x - _mainScrollPos.x);

                    if ((newSlideIndexOffset < 0 && delta > 0) ||
                        (newSlideIndexOffset >= _getNumItems() - 1 && delta < 0)) {
                        x = _mainScrollPos.x + delta * _options.mainScrollEndFriction;
                    }
                }

                _mainScrollPos.x = x;
                _setTranslateX(x, _containerStyle);
            },
            _calculatePanOffset = function (axis, zoomLevel) {
                var m = _midZoomPoint[axis] - _offset[axis];
                return _startPanOffset[axis] + _currPanDist[axis] + m - m * (zoomLevel / _startZoomLevel);
            },

            _equalizePoints = function (p1, p2) {
                p1.x = p2.x;
                p1.y = p2.y;
                if (p2.id) {
                    p1.id = p2.id;
                }
            },
            _roundPoint = function (p) {
                p.x = Math.round(p.x);
                p.y = Math.round(p.y);
            },

            _mouseMoveTimeout = null,
            _onFirstMouseMove = function () {
                // Wait until mouse move event is fired at least twice during 100ms
                // We do this, because some mobile browsers trigger it on touchstart
                if (_mouseMoveTimeout) {
                    framework.unbind(document, 'mousemove', _onFirstMouseMove);
                    framework.addClass(template, 'pswp--has_mouse');
                    _options.mouseUsed = true;
                    _shout('mouseUsed');
                }
                _mouseMoveTimeout = setTimeout(function () {
                    _mouseMoveTimeout = null;
                }, 100);
            },

            _bindEvents = function () {
                framework.bind(document, 'keydown', self);

                if (_features.transform) {
                    // don't bind click event in browsers that don't support transform (mostly IE8)
                    framework.bind(self.scrollWrap, 'click', self);
                }


                if (!_options.mouseUsed) {
                    framework.bind(document, 'mousemove', _onFirstMouseMove);
                }

                framework.bind(window, 'resize scroll', self);

                _shout('bindEvents');
            },

            _unbindEvents = function () {
                framework.unbind(window, 'resize', self);
                framework.unbind(window, 'scroll', _globalEventHandlers.scroll);
                framework.unbind(document, 'keydown', self);
                framework.unbind(document, 'mousemove', _onFirstMouseMove);

                if (_features.transform) {
                    framework.unbind(self.scrollWrap, 'click', self);
                }

                if (_isDragging) {
                    framework.unbind(window, _upMoveEvents, self);
                }

                _shout('unbindEvents');
            },

            _calculatePanBounds = function (zoomLevel, update) {
                var bounds = _calculateItemSize(self.currItem, _viewportSize, zoomLevel);
                if (update) {
                    _currPanBounds = bounds;
                }
                return bounds;
            },

            _getMinZoomLevel = function (item) {
                if (!item) {
                    item = self.currItem;
                }
                return item.initialZoomLevel;
            },
            _getMaxZoomLevel = function (item) {
                if (!item) {
                    item = self.currItem;
                }
                return item.w > 0 ? _options.maxSpreadZoom : 1;
            },

            // Return true if offset is out of the bounds
            _modifyDestPanOffset = function (axis, destPanBounds, destPanOffset, destZoomLevel) {
                if (destZoomLevel === self.currItem.initialZoomLevel) {
                    destPanOffset[axis] = self.currItem.initialPosition[axis];
                    return true;
                } else {
                    destPanOffset[axis] = _calculatePanOffset(axis, destZoomLevel);

                    if (destPanOffset[axis] > destPanBounds.min[axis]) {
                        destPanOffset[axis] = destPanBounds.min[axis];
                        return true;
                    } else if (destPanOffset[axis] < destPanBounds.max[axis]) {
                        destPanOffset[axis] = destPanBounds.max[axis];
                        return true;
                    }
                }
                return false;
            },

            _setupTransforms = function () {

                if (_transformKey) {
                    // setup 3d transforms
                    var allow3dTransform = _features.perspective && !_likelyTouchDevice;
                    _translatePrefix = 'translate' + (allow3dTransform ? '3d(' : '(');
                    _translateSufix = _features.perspective ? ', 0px)' : ')';
                    return;
                }

                // Override zoom/pan/move functions in case old browser is used (most likely IE)
                // (so they use left/top/width/height, instead of CSS transform)

                _transformKey = 'left';
                framework.addClass(template, 'pswp--ie');

                _setTranslateX = function (x, elStyle) {
                    elStyle.left = x + 'px';
                };
                _applyZoomPanToItem = function (item) {

                    var zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
                        s = item.container.style,
                        w = zoomRatio * item.w,
                        h = zoomRatio * item.h;

                    s.width = w + 'px';
                    s.height = h + 'px';
                    s.left = item.initialPosition.x + 'px';
                    s.top = item.initialPosition.y + 'px';

                };
                _applyCurrentZoomPan = function () {
                    if (_currZoomElementStyle) {

                        var s = _currZoomElementStyle,
                            item = self.currItem,
                            zoomRatio = item.fitRatio > 1 ? 1 : item.fitRatio,
                            w = zoomRatio * item.w,
                            h = zoomRatio * item.h;

                        s.width = w + 'px';
                        s.height = h + 'px';


                        s.left = _panOffset.x + 'px';
                        s.top = _panOffset.y + 'px';
                    }

                };
            },

            _onKeyDown = function (e) {
                var keydownAction = '';
                if (_options.escKey && e.keyCode === 27) {
                    keydownAction = 'close';
                } else if (_options.arrowKeys) {
                    if (e.keyCode === 37) {
                        keydownAction = 'prev';
                    } else if (e.keyCode === 39) {
                        keydownAction = 'next';
                    }
                }

                if (keydownAction) {
                    // don't do anything if special key pressed to prevent from overriding default browser actions
                    // e.g. in Chrome on Mac cmd+arrow-left returns to previous page
                    if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
                        if (e.preventDefault) {
                            e.preventDefault();
                        } else {
                            e.returnValue = false;
                        }
                        self[keydownAction]();
                    }
                }
            },

            _onGlobalClick = function (e) {
                if (!e) {
                    return;
                }

                // don't allow click event to pass through when triggering after drag or some other gesture
                if (_moved || _zoomStarted || _mainScrollAnimating || _verticalDragInitiated) {
                    e.preventDefault();
                    e.stopPropagation();
                }
            },

            _updatePageScrollOffset = function () {
                self.setScrollOffset(0, framework.getScrollY());
            };







        // Micro animation engine
        var _animations = {},
            _numAnimations = 0,
            _stopAnimation = function (name) {
                if (_animations[name]) {
                    if (_animations[name].raf) {
                        _cancelAF(_animations[name].raf);
                    }
                    _numAnimations--;
                    delete _animations[name];
                }
            },
            _registerStartAnimation = function (name) {
                if (_animations[name]) {
                    _stopAnimation(name);
                }
                if (!_animations[name]) {
                    _numAnimations++;
                    _animations[name] = {};
                }
            },
            _stopAllAnimations = function () {
                for (var prop in _animations) {

                    if (_animations.hasOwnProperty(prop)) {
                        _stopAnimation(prop);
                    }

                }
            },
            _animateProp = function (name, b, endProp, d, easingFn, onUpdate, onComplete) {
                var startAnimTime = _getCurrentTime(), t;
                _registerStartAnimation(name);

                var animloop = function () {
                    if (_animations[name]) {

                        t = _getCurrentTime() - startAnimTime; // time diff
                        //b - beginning (start prop)
                        //d - anim duration

                        if (t >= d) {
                            _stopAnimation(name);
                            onUpdate(endProp);
                            if (onComplete) {
                                onComplete();
                            }
                            return;
                        }
                        onUpdate((endProp - b) * easingFn(t / d) + b);

                        _animations[name].raf = _requestAF(animloop);
                    }
                };
                animloop();
            };



        var publicMethods = {

            // make a few local variables and functions public
            shout: _shout,
            listen: _listen,
            viewportSize: _viewportSize,
            options: _options,

            isMainScrollAnimating: function () {
                return _mainScrollAnimating;
            },
            getZoomLevel: function () {
                return _currZoomLevel;
            },
            getCurrentIndex: function () {
                return _currentItemIndex;
            },
            isDragging: function () {
                return _isDragging;
            },
            isZooming: function () {
                return _isZooming;
            },
            setScrollOffset: function (x, y) {
                _offset.x = x;
                _currentWindowScrollY = _offset.y = y;
                _shout('updateScrollOffset', _offset);
            },
            applyZoomPan: function (zoomLevel, panX, panY, allowRenderResolution) {
                _panOffset.x = panX;
                _panOffset.y = panY;
                _currZoomLevel = zoomLevel;
                _applyCurrentZoomPan(allowRenderResolution);
            },

            init: function () {

                if (_isOpen || _isDestroying) {
                    return;
                }

                var i;

                self.framework = framework; // basic functionality
                self.template = template; // root DOM element of PhotoSwipe
                self.bg = framework.getChildByClass(template, 'pswp__bg');

                _initalClassName = template.className;
                _isOpen = true;

                _features = framework.detectFeatures();
                _requestAF = _features.raf;
                _cancelAF = _features.caf;
                _transformKey = _features.transform;
                _oldIE = _features.oldIE;

                self.scrollWrap = framework.getChildByClass(template, 'pswp__scroll-wrap');
                self.container = framework.getChildByClass(self.scrollWrap, 'pswp__container');

                _containerStyle = self.container.style; // for fast access

                // Objects that hold slides (there are only 3 in DOM)
                self.itemHolders = _itemHolders = [
                    { el: self.container.children[0], wrap: 0, index: -1 },
                    { el: self.container.children[1], wrap: 0, index: -1 },
                    { el: self.container.children[2], wrap: 0, index: -1 }
                ];

                // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)
                _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'none';

                _setupTransforms();

                // Setup global events
                _globalEventHandlers = {
                    resize: self.updateSize,
                    scroll: _updatePageScrollOffset,
                    keydown: _onKeyDown,
                    click: _onGlobalClick
                };

                // disable show/hide effects on old browsers that don't support CSS animations or transforms, 
                // old IOS, Android and Opera mobile. Blackberry seems to work fine, even older models.
                var oldPhone = _features.isOldIOSPhone || _features.isOldAndroid || _features.isMobileOpera;
                if (!_features.animationName || !_features.transform || oldPhone) {
                    _options.showAnimationDuration = _options.hideAnimationDuration = 0;
                }

                // init modules
                for (i = 0; i < _modules.length; i++) {
                    self['init' + _modules[i]]();
                }

                // init
                if (UiClass) {
                    var ui = self.ui = new UiClass(self, framework);
                    ui.init();
                }

                _shout('firstUpdate');
                _currentItemIndex = _currentItemIndex || _options.index || 0;
                // validate index
                if (isNaN(_currentItemIndex) || _currentItemIndex < 0 || _currentItemIndex >= _getNumItems()) {
                    _currentItemIndex = 0;
                }
                self.currItem = _getItemAt(_currentItemIndex);


                if (_features.isOldIOSPhone || _features.isOldAndroid) {
                    _isFixedPosition = false;
                }

                template.setAttribute('aria-hidden', 'false');
                if (_options.modal) {
                    if (!_isFixedPosition) {
                        template.style.position = 'absolute';
                        template.style.top = framework.getScrollY() + 'px';
                    } else {
                        template.style.position = 'fixed';
                    }
                }

                if (_currentWindowScrollY === undefined) {
                    _shout('initialLayout');
                    _currentWindowScrollY = _initalWindowScrollY = framework.getScrollY();
                }

                // add classes to root element of PhotoSwipe
                var rootClasses = 'pswp--open ';
                if (_options.mainClass) {
                    rootClasses += _options.mainClass + ' ';
                }
                if (_options.showHideOpacity) {
                    rootClasses += 'pswp--animate_opacity ';
                }
                rootClasses += _likelyTouchDevice ? 'pswp--touch' : 'pswp--notouch';
                rootClasses += _features.animationName ? ' pswp--css_animation' : '';
                rootClasses += _features.svg ? ' pswp--svg' : '';
                framework.addClass(template, rootClasses);

                self.updateSize();

                // initial update
                _containerShiftIndex = -1;
                _indexDiff = null;
                for (i = 0; i < NUM_HOLDERS; i++) {
                    _setTranslateX((i + _containerShiftIndex) * _slideSize.x, _itemHolders[i].el.style);
                }

                if (!_oldIE) {
                    framework.bind(self.scrollWrap, _downEvents, self); // no dragging for old IE
                }

                _listen('initialZoomInEnd', function () {
                    self.setContent(_itemHolders[0], _currentItemIndex - 1);
                    self.setContent(_itemHolders[2], _currentItemIndex + 1);

                    _itemHolders[0].el.style.display = _itemHolders[2].el.style.display = 'block';

                    if (_options.focus) {
                        // focus causes layout, 
                        // which causes lag during the animation, 
                        // that's why we delay it untill the initial zoom transition ends
                        template.focus();
                    }


                    _bindEvents();
                });

                // set content for center slide (first time)
                self.setContent(_itemHolders[1], _currentItemIndex);

                self.updateCurrItem();

                _shout('afterInit');

                if (!_isFixedPosition) {

                    // On all versions of iOS lower than 8.0, we check size of viewport every second.
                    // 
                    // This is done to detect when Safari top & bottom bars appear, 
                    // as this action doesn't trigger any events (like resize). 
                    // 
                    // On iOS8 they fixed this.
                    // 
                    // 10 Nov 2014: iOS 7 usage ~40%. iOS 8 usage 56%.

                    _updateSizeInterval = setInterval(function () {
                        if (!_numAnimations && !_isDragging && !_isZooming && (_currZoomLevel === self.currItem.initialZoomLevel)) {
                            self.updateSize();
                        }
                    }, 1000);
                }

                framework.addClass(template, 'pswp--visible');
            },

            // Close the gallery, then destroy it
            close: function () {
                if (!_isOpen) {
                    return;
                }

                _isOpen = false;
                _isDestroying = true;
                _shout('close');
                _unbindEvents();

                _showOrHide(self.currItem, null, true, self.destroy);
            },

            // destroys the gallery (unbinds events, cleans up intervals and timeouts to avoid memory leaks)
            destroy: function () {
                _shout('destroy');

                if (_showOrHideTimeout) {
                    clearTimeout(_showOrHideTimeout);
                }

                template.setAttribute('aria-hidden', 'true');
                template.className = _initalClassName;

                if (_updateSizeInterval) {
                    clearInterval(_updateSizeInterval);
                }

                framework.unbind(self.scrollWrap, _downEvents, self);

                // we unbind scroll event at the end, as closing animation may depend on it
                framework.unbind(window, 'scroll', self);

                _stopDragUpdateLoop();

                _stopAllAnimations();

                _listeners = null;
            },

            /**
             * Pan image to position
             * @param {Number} x     
             * @param {Number} y     
             * @param {Boolean} force Will ignore bounds if set to true.
             */
            panTo: function (x, y, force) {
                if (!force) {
                    if (x > _currPanBounds.min.x) {
                        x = _currPanBounds.min.x;
                    } else if (x < _currPanBounds.max.x) {
                        x = _currPanBounds.max.x;
                    }

                    if (y > _currPanBounds.min.y) {
                        y = _currPanBounds.min.y;
                    } else if (y < _currPanBounds.max.y) {
                        y = _currPanBounds.max.y;
                    }
                }

                _panOffset.x = x;
                _panOffset.y = y;
                _applyCurrentZoomPan();
            },

            handleEvent: function (e) {
                e = e || window.event;
                if (_globalEventHandlers[e.type]) {
                    _globalEventHandlers[e.type](e);
                }
            },


            goTo: function (index) {

                index = _getLoopedId(index);

                var diff = index - _currentItemIndex;
                _indexDiff = diff;

                _currentItemIndex = index;
                self.currItem = _getItemAt(_currentItemIndex);
                _currPositionIndex -= diff;

                _moveMainScroll(_slideSize.x * _currPositionIndex);


                _stopAllAnimations();
                _mainScrollAnimating = false;

                self.updateCurrItem();
            },
            next: function () {
                self.goTo(_currentItemIndex + 1);
            },
            prev: function () {
                self.goTo(_currentItemIndex - 1);
            },

            // update current zoom/pan objects
            updateCurrZoomItem: function (emulateSetContent) {
                if (emulateSetContent) {
                    _shout('beforeChange', 0);
                }

                // itemHolder[1] is middle (current) item
                if (_itemHolders[1].el.children.length) {
                    var zoomElement = _itemHolders[1].el.children[0];
                    if (framework.hasClass(zoomElement, 'pswp__zoom-wrap')) {
                        _currZoomElementStyle = zoomElement.style;
                    } else {
                        _currZoomElementStyle = null;
                    }
                } else {
                    _currZoomElementStyle = null;
                }

                _currPanBounds = self.currItem.bounds;
                _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;

                _panOffset.x = _currPanBounds.center.x;
                _panOffset.y = _currPanBounds.center.y;

                if (emulateSetContent) {
                    _shout('afterChange');
                }
            },


            invalidateCurrItems: function () {
                _itemsNeedUpdate = true;
                for (var i = 0; i < NUM_HOLDERS; i++) {
                    if (_itemHolders[i].item) {
                        _itemHolders[i].item.needsUpdate = true;
                    }
                }
            },

            updateCurrItem: function (beforeAnimation) {

                if (_indexDiff === 0) {
                    return;
                }

                var diffAbs = Math.abs(_indexDiff),
                    tempHolder;

                if (beforeAnimation && diffAbs < 2) {
                    return;
                }


                self.currItem = _getItemAt(_currentItemIndex);
                _renderMaxResolution = false;

                _shout('beforeChange', _indexDiff);

                if (diffAbs >= NUM_HOLDERS) {
                    _containerShiftIndex += _indexDiff + (_indexDiff > 0 ? -NUM_HOLDERS : NUM_HOLDERS);
                    diffAbs = NUM_HOLDERS;
                }
                for (var i = 0; i < diffAbs; i++) {
                    if (_indexDiff > 0) {
                        tempHolder = _itemHolders.shift();
                        _itemHolders[NUM_HOLDERS - 1] = tempHolder; // move first to last

                        _containerShiftIndex++;
                        _setTranslateX((_containerShiftIndex + 2) * _slideSize.x, tempHolder.el.style);
                        self.setContent(tempHolder, _currentItemIndex - diffAbs + i + 1 + 1);
                    } else {
                        tempHolder = _itemHolders.pop();
                        _itemHolders.unshift(tempHolder); // move last to first

                        _containerShiftIndex--;
                        _setTranslateX(_containerShiftIndex * _slideSize.x, tempHolder.el.style);
                        self.setContent(tempHolder, _currentItemIndex + diffAbs - i - 1 - 1);
                    }

                }

                // reset zoom/pan on previous item
                if (_currZoomElementStyle && Math.abs(_indexDiff) === 1) {

                    var prevItem = _getItemAt(_prevItemIndex);
                    if (prevItem.initialZoomLevel !== _currZoomLevel) {
                        _calculateItemSize(prevItem, _viewportSize);
                        _setImageSize(prevItem);
                        _applyZoomPanToItem(prevItem);
                    }

                }

                // reset diff after update
                _indexDiff = 0;

                self.updateCurrZoomItem();

                _prevItemIndex = _currentItemIndex;

                _shout('afterChange');

            },



            updateSize: function (force) {

                if (!_isFixedPosition && _options.modal) {
                    var windowScrollY = framework.getScrollY();
                    if (_currentWindowScrollY !== windowScrollY) {
                        template.style.top = windowScrollY + 'px';
                        _currentWindowScrollY = windowScrollY;
                    }
                    if (!force && _windowVisibleSize.x === window.innerWidth && _windowVisibleSize.y === window.innerHeight) {
                        return;
                    }
                    _windowVisibleSize.x = window.innerWidth;
                    _windowVisibleSize.y = window.innerHeight;

                    //template.style.width = _windowVisibleSize.x + 'px';
                    template.style.height = _windowVisibleSize.y + 'px';
                }



                _viewportSize.x = self.scrollWrap.clientWidth;
                _viewportSize.y = self.scrollWrap.clientHeight;

                _updatePageScrollOffset();

                _slideSize.x = _viewportSize.x + Math.round(_viewportSize.x * _options.spacing);
                _slideSize.y = _viewportSize.y;

                _moveMainScroll(_slideSize.x * _currPositionIndex);

                _shout('beforeResize'); // even may be used for example to switch image sources


                // don't re-calculate size on inital size update
                if (_containerShiftIndex !== undefined) {

                    var holder,
                        item,
                        hIndex;

                    for (var i = 0; i < NUM_HOLDERS; i++) {
                        holder = _itemHolders[i];
                        _setTranslateX((i + _containerShiftIndex) * _slideSize.x, holder.el.style);

                        hIndex = _currentItemIndex + i - 1;

                        if (_options.loop && _getNumItems() > 2) {
                            hIndex = _getLoopedId(hIndex);
                        }

                        // update zoom level on items and refresh source (if needsUpdate)
                        item = _getItemAt(hIndex);

                        // re-render gallery item if `needsUpdate`,
                        // or doesn't have `bounds` (entirely new slide object)
                        if (item && (_itemsNeedUpdate || item.needsUpdate || !item.bounds)) {

                            self.cleanSlide(item);

                            self.setContent(holder, hIndex);

                            // if "center" slide
                            if (i === 1) {
                                self.currItem = item;
                                self.updateCurrZoomItem(true);
                            }

                            item.needsUpdate = false;

                        } else if (holder.index === -1 && hIndex >= 0) {
                            // add content first time
                            self.setContent(holder, hIndex);
                        }
                        if (item && item.container) {
                            _calculateItemSize(item, _viewportSize);
                            _setImageSize(item);
                            _applyZoomPanToItem(item);
                        }

                    }
                    _itemsNeedUpdate = false;
                }

                _startZoomLevel = _currZoomLevel = self.currItem.initialZoomLevel;
                _currPanBounds = self.currItem.bounds;

                if (_currPanBounds) {
                    _panOffset.x = _currPanBounds.center.x;
                    _panOffset.y = _currPanBounds.center.y;
                    _applyCurrentZoomPan(true);
                }

                _shout('resize');
            },

            // Zoom current item to
            zoomTo: function (destZoomLevel, centerPoint, speed, easingFn, updateFn) {
                /*
                    if(destZoomLevel === 'fit') {
                        destZoomLevel = self.currItem.fitRatio;
                    } else if(destZoomLevel === 'fill') {
                        destZoomLevel = self.currItem.fillRatio;
                    }
                */

                if (centerPoint) {
                    _startZoomLevel = _currZoomLevel;
                    _midZoomPoint.x = Math.abs(centerPoint.x) - _panOffset.x;
                    _midZoomPoint.y = Math.abs(centerPoint.y) - _panOffset.y;
                    _equalizePoints(_startPanOffset, _panOffset);
                }

                var destPanBounds = _calculatePanBounds(destZoomLevel, false),
                    destPanOffset = {};

                _modifyDestPanOffset('x', destPanBounds, destPanOffset, destZoomLevel);
                _modifyDestPanOffset('y', destPanBounds, destPanOffset, destZoomLevel);

                var initialZoomLevel = _currZoomLevel;
                var initialPanOffset = {
                    x: _panOffset.x,
                    y: _panOffset.y
                };

                _roundPoint(destPanOffset);

                var onUpdate = function (now) {
                    if (now === 1) {
                        _currZoomLevel = destZoomLevel;
                        _panOffset.x = destPanOffset.x;
                        _panOffset.y = destPanOffset.y;
                    } else {
                        _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
                        _panOffset.x = (destPanOffset.x - initialPanOffset.x) * now + initialPanOffset.x;
                        _panOffset.y = (destPanOffset.y - initialPanOffset.y) * now + initialPanOffset.y;
                    }

                    if (updateFn) {
                        updateFn(now);
                    }

                    _applyCurrentZoomPan(now === 1);
                };

                if (speed) {
                    _animateProp('customZoomTo', 0, 1, speed, easingFn || framework.easing.sine.inOut, onUpdate);
                } else {
                    onUpdate(1);
                }
            }


        };


        /*>>core*/

        /*>>gestures*/
        /**
         * Mouse/touch/pointer event handlers.
         * 
         * separated from @core.js for readability
         */

        var MIN_SWIPE_DISTANCE = 30,
            DIRECTION_CHECK_OFFSET = 10; // amount of pixels to drag to determine direction of swipe

        var _gestureStartTime,
            _gestureCheckSpeedTime,

            // pool of objects that are used during dragging of zooming
            p = {}, // first point
            p2 = {}, // second point (for zoom gesture)
            delta = {},
            _currPoint = {},
            _startPoint = {},
            _currPointers = [],
            _startMainScrollPos = {},
            _releaseAnimData,
            _posPoints = [], // array of points during dragging, used to determine type of gesture
            _tempPoint = {},

            _isZoomingIn,
            _verticalDragInitiated,
            _oldAndroidTouchEndTimeout,
            _currZoomedItemIndex = 0,
            _centerPoint = _getEmptyPoint(),
            _lastReleaseTime = 0,
            _isDragging, // at least one pointer is down
            _isMultitouch, // at least two _pointers are down
            _zoomStarted, // zoom level changed during zoom gesture
            _moved,
            _dragAnimFrame,
            _mainScrollShifted,
            _currentPoints, // array of current touch points
            _isZooming,
            _currPointsDistance,
            _startPointsDistance,
            _currPanBounds,
            _mainScrollPos = _getEmptyPoint(),
            _currZoomElementStyle,
            _mainScrollAnimating, // true, if animation after swipe gesture is running
            _midZoomPoint = _getEmptyPoint(),
            _currCenterPoint = _getEmptyPoint(),
            _direction,
            _isFirstMove,
            _opacityChanged,
            _bgOpacity,
            _wasOverInitialZoom,

            _isEqualPoints = function (p1, p2) {
                return p1.x === p2.x && p1.y === p2.y;
            },
            _isNearbyPoints = function (touch0, touch1) {
                return Math.abs(touch0.x - touch1.x) < DOUBLE_TAP_RADIUS && Math.abs(touch0.y - touch1.y) < DOUBLE_TAP_RADIUS;
            },
            _calculatePointsDistance = function (p1, p2) {
                _tempPoint.x = Math.abs(p1.x - p2.x);
                _tempPoint.y = Math.abs(p1.y - p2.y);
                return Math.sqrt(_tempPoint.x * _tempPoint.x + _tempPoint.y * _tempPoint.y);
            },
            _stopDragUpdateLoop = function () {
                if (_dragAnimFrame) {
                    _cancelAF(_dragAnimFrame);
                    _dragAnimFrame = null;
                }
            },
            _dragUpdateLoop = function () {
                if (_isDragging) {
                    _dragAnimFrame = _requestAF(_dragUpdateLoop);
                    _renderMovement();
                }
            },
            _canPan = function () {
                return !(_options.scaleMode === 'fit' && _currZoomLevel === self.currItem.initialZoomLevel);
            },

            // find the closest parent DOM element
            _closestElement = function (el, fn) {
                if (!el) {
                    return false;
                }

                // don't search elements above pswp__scroll-wrap
                if (el.className && el.className.indexOf('pswp__scroll-wrap') > -1) {
                    return false;
                }

                if (fn(el)) {
                    return el;
                }

                return _closestElement(el.parentNode, fn);
            },

            _preventObj = {},
            _preventDefaultEventBehaviour = function (e, isDown) {
                _preventObj.prevent = !_closestElement(e.target, _options.isClickableElement);

                _shout('preventDragEvent', e, isDown, _preventObj);
                return _preventObj.prevent;

            },
            _convertTouchToPoint = function (touch, p) {
                p.x = touch.pageX;
                p.y = touch.pageY;
                p.id = touch.identifier;
                return p;
            },
            _findCenterOfPoints = function (p1, p2, pCenter) {
                pCenter.x = (p1.x + p2.x) * 0.5;
                pCenter.y = (p1.y + p2.y) * 0.5;
            },
            _pushPosPoint = function (time, x, y) {
                if (time - _gestureCheckSpeedTime > 50) {
                    var o = _posPoints.length > 2 ? _posPoints.shift() : {};
                    o.x = x;
                    o.y = y;
                    _posPoints.push(o);
                    _gestureCheckSpeedTime = time;
                }
            },

            _calculateVerticalDragOpacityRatio = function () {
                var yOffset = _panOffset.y - self.currItem.initialPosition.y; // difference between initial and current position
                return 1 - Math.abs(yOffset / (_viewportSize.y / 2));
            },


            // points pool, reused during touch events
            _ePoint1 = {},
            _ePoint2 = {},
            _tempPointsArr = [],
            _tempCounter,
            _getTouchPoints = function (e) {
                // clean up previous points, without recreating array
                while (_tempPointsArr.length > 0) {
                    _tempPointsArr.pop();
                }

                if (!_pointerEventEnabled) {
                    if (e.type.indexOf('touch') > -1) {

                        if (e.touches && e.touches.length > 0) {
                            _tempPointsArr[0] = _convertTouchToPoint(e.touches[0], _ePoint1);
                            if (e.touches.length > 1) {
                                _tempPointsArr[1] = _convertTouchToPoint(e.touches[1], _ePoint2);
                            }
                        }

                    } else {
                        _ePoint1.x = e.pageX;
                        _ePoint1.y = e.pageY;
                        _ePoint1.id = '';
                        _tempPointsArr[0] = _ePoint1;//_ePoint1;
                    }
                } else {
                    _tempCounter = 0;
                    // we can use forEach, as pointer events are supported only in modern browsers
                    _currPointers.forEach(function (p) {
                        if (_tempCounter === 0) {
                            _tempPointsArr[0] = p;
                        } else if (_tempCounter === 1) {
                            _tempPointsArr[1] = p;
                        }
                        _tempCounter++;

                    });
                }
                return _tempPointsArr;
            },

            _panOrMoveMainScroll = function (axis, delta) {

                var panFriction,
                    overDiff = 0,
                    newOffset = _panOffset[axis] + delta[axis],
                    startOverDiff,
                    dir = delta[axis] > 0,
                    newMainScrollPosition = _mainScrollPos.x + delta.x,
                    mainScrollDiff = _mainScrollPos.x - _startMainScrollPos.x,
                    newPanPos,
                    newMainScrollPos;

                // calculate fdistance over the bounds and friction
                if (newOffset > _currPanBounds.min[axis] || newOffset < _currPanBounds.max[axis]) {
                    panFriction = _options.panEndFriction;
                    // Linear increasing of friction, so at 1/4 of viewport it's at max value. 
                    // Looks not as nice as was expected. Left for history.
                    // panFriction = (1 - (_panOffset[axis] + delta[axis] + panBounds.min[axis]) / (_viewportSize[axis] / 4) );
                } else {
                    panFriction = 1;
                }

                newOffset = _panOffset[axis] + delta[axis] * panFriction;

                // move main scroll or start panning
                if (_options.allowPanToNext || _currZoomLevel === self.currItem.initialZoomLevel) {


                    if (!_currZoomElementStyle) {

                        newMainScrollPos = newMainScrollPosition;

                    } else if (_direction === 'h' && axis === 'x' && !_zoomStarted) {

                        if (dir) {
                            if (newOffset > _currPanBounds.min[axis]) {
                                panFriction = _options.panEndFriction;
                                overDiff = _currPanBounds.min[axis] - newOffset;
                                startOverDiff = _currPanBounds.min[axis] - _startPanOffset[axis];
                            }

                            // drag right
                            if ((startOverDiff <= 0 || mainScrollDiff < 0) && _getNumItems() > 1) {
                                newMainScrollPos = newMainScrollPosition;
                                if (mainScrollDiff < 0 && newMainScrollPosition > _startMainScrollPos.x) {
                                    newMainScrollPos = _startMainScrollPos.x;
                                }
                            } else {
                                if (_currPanBounds.min.x !== _currPanBounds.max.x) {
                                    newPanPos = newOffset;
                                }

                            }

                        } else {

                            if (newOffset < _currPanBounds.max[axis]) {
                                panFriction = _options.panEndFriction;
                                overDiff = newOffset - _currPanBounds.max[axis];
                                startOverDiff = _startPanOffset[axis] - _currPanBounds.max[axis];
                            }

                            if ((startOverDiff <= 0 || mainScrollDiff > 0) && _getNumItems() > 1) {
                                newMainScrollPos = newMainScrollPosition;

                                if (mainScrollDiff > 0 && newMainScrollPosition < _startMainScrollPos.x) {
                                    newMainScrollPos = _startMainScrollPos.x;
                                }

                            } else {
                                if (_currPanBounds.min.x !== _currPanBounds.max.x) {
                                    newPanPos = newOffset;
                                }
                            }

                        }


                        //
                    }

                    if (axis === 'x') {

                        if (newMainScrollPos !== undefined) {
                            _moveMainScroll(newMainScrollPos, true);
                            if (newMainScrollPos === _startMainScrollPos.x) {
                                _mainScrollShifted = false;
                            } else {
                                _mainScrollShifted = true;
                            }
                        }

                        if (_currPanBounds.min.x !== _currPanBounds.max.x) {
                            if (newPanPos !== undefined) {
                                _panOffset.x = newPanPos;
                            } else if (!_mainScrollShifted) {
                                _panOffset.x += delta.x * panFriction;
                            }
                        }

                        return newMainScrollPos !== undefined;
                    }

                }

                if (!_mainScrollAnimating) {

                    if (!_mainScrollShifted) {
                        if (_currZoomLevel > self.currItem.fitRatio) {
                            _panOffset[axis] += delta[axis] * panFriction;

                        }
                    }


                }

            },

            // Pointerdown/touchstart/mousedown handler
            _onDragStart = function (e) {

                // Allow dragging only via left mouse button.
                // As this handler is not added in IE8 - we ignore e.which
                // 
                // http://www.quirksmode.org/js/events_properties.html
                // https://developer.mozilla.org/en-US/docs/Web/API/event.button
                if (e.type === 'mousedown' && e.button > 0) {
                    return;
                }

                if (_initialZoomRunning) {
                    e.preventDefault();
                    return;
                }

                if (_oldAndroidTouchEndTimeout && e.type === 'mousedown') {
                    return;
                }

                if (_preventDefaultEventBehaviour(e, true)) {
                    e.preventDefault();
                }



                _shout('pointerDown');

                if (_pointerEventEnabled) {
                    var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
                    if (pointerIndex < 0) {
                        pointerIndex = _currPointers.length;
                    }
                    _currPointers[pointerIndex] = { x: e.pageX, y: e.pageY, id: e.pointerId };
                }



                var startPointsList = _getTouchPoints(e),
                    numPoints = startPointsList.length;

                _currentPoints = null;

                _stopAllAnimations();

                // init drag
                if (!_isDragging || numPoints === 1) {



                    _isDragging = _isFirstMove = true;
                    framework.bind(window, _upMoveEvents, self);

                    _isZoomingIn =
                        _wasOverInitialZoom =
                        _opacityChanged =
                        _verticalDragInitiated =
                        _mainScrollShifted =
                        _moved =
                        _isMultitouch =
                        _zoomStarted = false;

                    _direction = null;

                    _shout('firstTouchStart', startPointsList);

                    _equalizePoints(_startPanOffset, _panOffset);

                    _currPanDist.x = _currPanDist.y = 0;
                    _equalizePoints(_currPoint, startPointsList[0]);
                    _equalizePoints(_startPoint, _currPoint);

                    //_equalizePoints(_startMainScrollPos, _mainScrollPos);
                    _startMainScrollPos.x = _slideSize.x * _currPositionIndex;

                    _posPoints = [{
                        x: _currPoint.x,
                        y: _currPoint.y
                    }];

                    _gestureCheckSpeedTime = _gestureStartTime = _getCurrentTime();

                    //_mainScrollAnimationEnd(true);
                    _calculatePanBounds(_currZoomLevel, true);

                    // Start rendering
                    _stopDragUpdateLoop();
                    _dragUpdateLoop();

                }

                // init zoom
                if (!_isZooming && numPoints > 1 && !_mainScrollAnimating && !_mainScrollShifted) {
                    _startZoomLevel = _currZoomLevel;
                    _zoomStarted = false; // true if zoom changed at least once

                    _isZooming = _isMultitouch = true;
                    _currPanDist.y = _currPanDist.x = 0;

                    _equalizePoints(_startPanOffset, _panOffset);

                    _equalizePoints(p, startPointsList[0]);
                    _equalizePoints(p2, startPointsList[1]);

                    _findCenterOfPoints(p, p2, _currCenterPoint);

                    _midZoomPoint.x = Math.abs(_currCenterPoint.x) - _panOffset.x;
                    _midZoomPoint.y = Math.abs(_currCenterPoint.y) - _panOffset.y;
                    _currPointsDistance = _startPointsDistance = _calculatePointsDistance(p, p2);
                }


            },

            // Pointermove/touchmove/mousemove handler
            _onDragMove = function (e) {

                e.preventDefault();

                if (_pointerEventEnabled) {
                    var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');
                    if (pointerIndex > -1) {
                        var p = _currPointers[pointerIndex];
                        p.x = e.pageX;
                        p.y = e.pageY;
                    }
                }

                if (_isDragging) {
                    var touchesList = _getTouchPoints(e);
                    if (!_direction && !_moved && !_isZooming) {

                        if (_mainScrollPos.x !== _slideSize.x * _currPositionIndex) {
                            // if main scroll position is shifted â€“ direction is always horizontal
                            _direction = 'h';
                        } else {
                            var diff = Math.abs(touchesList[0].x - _currPoint.x) - Math.abs(touchesList[0].y - _currPoint.y);
                            // check the direction of movement
                            if (Math.abs(diff) >= DIRECTION_CHECK_OFFSET) {
                                _direction = diff > 0 ? 'h' : 'v';
                                _currentPoints = touchesList;
                            }
                        }

                    } else {
                        _currentPoints = touchesList;
                    }
                }
            },
            // 
            _renderMovement = function () {

                if (!_currentPoints) {
                    return;
                }

                var numPoints = _currentPoints.length;

                if (numPoints === 0) {
                    return;
                }

                _equalizePoints(p, _currentPoints[0]);

                delta.x = p.x - _currPoint.x;
                delta.y = p.y - _currPoint.y;

                if (_isZooming && numPoints > 1) {
                    // Handle behaviour for more than 1 point

                    _currPoint.x = p.x;
                    _currPoint.y = p.y;

                    // check if one of two points changed
                    if (!delta.x && !delta.y && _isEqualPoints(_currentPoints[1], p2)) {
                        return;
                    }

                    _equalizePoints(p2, _currentPoints[1]);


                    if (!_zoomStarted) {
                        _zoomStarted = true;
                        _shout('zoomGestureStarted');
                    }

                    // Distance between two points
                    var pointsDistance = _calculatePointsDistance(p, p2);

                    var zoomLevel = _calculateZoomLevel(pointsDistance);

                    // slightly over the of initial zoom level
                    if (zoomLevel > self.currItem.initialZoomLevel + self.currItem.initialZoomLevel / 15) {
                        _wasOverInitialZoom = true;
                    }

                    // Apply the friction if zoom level is out of the bounds
                    var zoomFriction = 1,
                        minZoomLevel = _getMinZoomLevel(),
                        maxZoomLevel = _getMaxZoomLevel();

                    if (zoomLevel < minZoomLevel) {

                        if (_options.pinchToClose && !_wasOverInitialZoom && _startZoomLevel <= self.currItem.initialZoomLevel) {
                            // fade out background if zooming out
                            var minusDiff = minZoomLevel - zoomLevel;
                            var percent = 1 - minusDiff / (minZoomLevel / 1.2);

                            _applyBgOpacity(percent);
                            _shout('onPinchClose', percent);
                            _opacityChanged = true;
                        } else {
                            zoomFriction = (minZoomLevel - zoomLevel) / minZoomLevel;
                            if (zoomFriction > 1) {
                                zoomFriction = 1;
                            }
                            zoomLevel = minZoomLevel - zoomFriction * (minZoomLevel / 3);
                        }

                    } else if (zoomLevel > maxZoomLevel) {
                        // 1.5 - extra zoom level above the max. E.g. if max is x6, real max 6 + 1.5 = 7.5
                        zoomFriction = (zoomLevel - maxZoomLevel) / (minZoomLevel * 6);
                        if (zoomFriction > 1) {
                            zoomFriction = 1;
                        }
                        zoomLevel = maxZoomLevel + zoomFriction * minZoomLevel;
                    }

                    if (zoomFriction < 0) {
                        zoomFriction = 0;
                    }

                    // distance between touch points after friction is applied
                    _currPointsDistance = pointsDistance;

                    // _centerPoint - The point in the middle of two pointers
                    _findCenterOfPoints(p, p2, _centerPoint);

                    // paning with two pointers pressed
                    _currPanDist.x += _centerPoint.x - _currCenterPoint.x;
                    _currPanDist.y += _centerPoint.y - _currCenterPoint.y;
                    _equalizePoints(_currCenterPoint, _centerPoint);

                    _panOffset.x = _calculatePanOffset('x', zoomLevel);
                    _panOffset.y = _calculatePanOffset('y', zoomLevel);

                    _isZoomingIn = zoomLevel > _currZoomLevel;
                    _currZoomLevel = zoomLevel;
                    _applyCurrentZoomPan();

                } else {

                    // handle behaviour for one point (dragging or panning)

                    if (!_direction) {
                        return;
                    }

                    if (_isFirstMove) {
                        _isFirstMove = false;

                        // subtract drag distance that was used during the detection direction  

                        if (Math.abs(delta.x) >= DIRECTION_CHECK_OFFSET) {
                            delta.x -= _currentPoints[0].x - _startPoint.x;
                        }

                        if (Math.abs(delta.y) >= DIRECTION_CHECK_OFFSET) {
                            delta.y -= _currentPoints[0].y - _startPoint.y;
                        }
                    }

                    _currPoint.x = p.x;
                    _currPoint.y = p.y;

                    // do nothing if pointers position hasn't changed
                    if (delta.x === 0 && delta.y === 0) {
                        return;
                    }

                    if (_direction === 'v' && _options.closeOnVerticalDrag) {
                        if (!_canPan()) {
                            _currPanDist.y += delta.y;
                            _panOffset.y += delta.y;

                            var opacityRatio = _calculateVerticalDragOpacityRatio();

                            _verticalDragInitiated = true;
                            _shout('onVerticalDrag', opacityRatio);

                            _applyBgOpacity(opacityRatio);
                            _applyCurrentZoomPan();
                            return;
                        }
                    }

                    _pushPosPoint(_getCurrentTime(), p.x, p.y);

                    _moved = true;
                    _currPanBounds = self.currItem.bounds;

                    var mainScrollChanged = _panOrMoveMainScroll('x', delta);
                    if (!mainScrollChanged) {
                        _panOrMoveMainScroll('y', delta);

                        _roundPoint(_panOffset);
                        _applyCurrentZoomPan();
                    }

                }

            },

            // Pointerup/pointercancel/touchend/touchcancel/mouseup event handler
            _onDragRelease = function (e) {

                if (_features.isOldAndroid) {

                    if (_oldAndroidTouchEndTimeout && e.type === 'mouseup') {
                        return;
                    }

                    // on Android (v4.1, 4.2, 4.3 & possibly older) 
                    // ghost mousedown/up event isn't preventable via e.preventDefault,
                    // which causes fake mousedown event
                    // so we block mousedown/up for 600ms
                    if (e.type.indexOf('touch') > -1) {
                        clearTimeout(_oldAndroidTouchEndTimeout);
                        _oldAndroidTouchEndTimeout = setTimeout(function () {
                            _oldAndroidTouchEndTimeout = 0;
                        }, 600);
                    }

                }

                _shout('pointerUp');

                if (_preventDefaultEventBehaviour(e, false)) {
                    e.preventDefault();
                }

                var releasePoint;

                if (_pointerEventEnabled) {
                    var pointerIndex = framework.arraySearch(_currPointers, e.pointerId, 'id');

                    if (pointerIndex > -1) {
                        releasePoint = _currPointers.splice(pointerIndex, 1)[0];

                        if (navigator.pointerEnabled) {
                            releasePoint.type = e.pointerType || 'mouse';
                        } else {
                            var MSPOINTER_TYPES = {
                                4: 'mouse', // event.MSPOINTER_TYPE_MOUSE
                                2: 'touch', // event.MSPOINTER_TYPE_TOUCH 
                                3: 'pen' // event.MSPOINTER_TYPE_PEN
                            };
                            releasePoint.type = MSPOINTER_TYPES[e.pointerType];

                            if (!releasePoint.type) {
                                releasePoint.type = e.pointerType || 'mouse';
                            }
                        }

                    }
                }

                var touchList = _getTouchPoints(e),
                    gestureType,
                    numPoints = touchList.length;

                if (e.type === 'mouseup') {
                    numPoints = 0;
                }

                // Do nothing if there were 3 touch points or more
                if (numPoints === 2) {
                    _currentPoints = null;
                    return true;
                }

                // if second pointer released
                if (numPoints === 1) {
                    _equalizePoints(_startPoint, touchList[0]);
                }


                // pointer hasn't moved, send "tap release" point
                if (numPoints === 0 && !_direction && !_mainScrollAnimating) {
                    if (!releasePoint) {
                        if (e.type === 'mouseup') {
                            releasePoint = { x: e.pageX, y: e.pageY, type: 'mouse' };
                        } else if (e.changedTouches && e.changedTouches[0]) {
                            releasePoint = { x: e.changedTouches[0].pageX, y: e.changedTouches[0].pageY, type: 'touch' };
                        }
                    }

                    _shout('touchRelease', e, releasePoint);
                }

                // Difference in time between releasing of two last touch points (zoom gesture)
                var releaseTimeDiff = -1;

                // Gesture completed, no pointers left
                if (numPoints === 0) {
                    _isDragging = false;
                    framework.unbind(window, _upMoveEvents, self);

                    _stopDragUpdateLoop();

                    if (_isZooming) {
                        // Two points released at the same time
                        releaseTimeDiff = 0;
                    } else if (_lastReleaseTime !== -1) {
                        releaseTimeDiff = _getCurrentTime() - _lastReleaseTime;
                    }
                }
                _lastReleaseTime = numPoints === 1 ? _getCurrentTime() : -1;

                if (releaseTimeDiff !== -1 && releaseTimeDiff < 150) {
                    gestureType = 'zoom';
                } else {
                    gestureType = 'swipe';
                }

                if (_isZooming && numPoints < 2) {
                    _isZooming = false;

                    // Only second point released
                    if (numPoints === 1) {
                        gestureType = 'zoomPointerUp';
                    }
                    _shout('zoomGestureEnded');
                }

                _currentPoints = null;
                if (!_moved && !_zoomStarted && !_mainScrollAnimating && !_verticalDragInitiated) {
                    // nothing to animate
                    return;
                }

                _stopAllAnimations();


                if (!_releaseAnimData) {
                    _releaseAnimData = _initDragReleaseAnimationData();
                }

                _releaseAnimData.calculateSwipeSpeed('x');


                if (_verticalDragInitiated) {

                    var opacityRatio = _calculateVerticalDragOpacityRatio();

                    if (opacityRatio < _options.verticalDragRange) {
                        self.close();
                    } else {
                        var initalPanY = _panOffset.y,
                            initialBgOpacity = _bgOpacity;

                        _animateProp('verticalDrag', 0, 1, 300, framework.easing.cubic.out, function (now) {

                            _panOffset.y = (self.currItem.initialPosition.y - initalPanY) * now + initalPanY;

                            _applyBgOpacity((1 - initialBgOpacity) * now + initialBgOpacity);
                            _applyCurrentZoomPan();
                        });

                        _shout('onVerticalDrag', 1);
                    }

                    return;
                }


                // main scroll 
                if ((_mainScrollShifted || _mainScrollAnimating) && numPoints === 0) {
                    var itemChanged = _finishSwipeMainScrollGesture(gestureType, _releaseAnimData);
                    if (itemChanged) {
                        return;
                    }
                    gestureType = 'zoomPointerUp';
                }

                // prevent zoom/pan animation when main scroll animation runs
                if (_mainScrollAnimating) {
                    return;
                }

                // Complete simple zoom gesture (reset zoom level if it's out of the bounds)  
                if (gestureType !== 'swipe') {
                    _completeZoomGesture();
                    return;
                }

                // Complete pan gesture if main scroll is not shifted, and it's possible to pan current image
                if (!_mainScrollShifted && _currZoomLevel > self.currItem.fitRatio) {
                    _completePanGesture(_releaseAnimData);
                }
            },


            // Returns object with data about gesture
            // It's created only once and then reused
            _initDragReleaseAnimationData = function () {
                // temp local vars
                var lastFlickDuration,
                    tempReleasePos;

                // s = this
                var s = {
                    lastFlickOffset: {},
                    lastFlickDist: {},
                    lastFlickSpeed: {},
                    slowDownRatio: {},
                    slowDownRatioReverse: {},
                    speedDecelerationRatio: {},
                    speedDecelerationRatioAbs: {},
                    distanceOffset: {},
                    backAnimDestination: {},
                    backAnimStarted: {},
                    calculateSwipeSpeed: function (axis) {


                        if (_posPoints.length > 1) {
                            lastFlickDuration = _getCurrentTime() - _gestureCheckSpeedTime + 50;
                            tempReleasePos = _posPoints[_posPoints.length - 2][axis];
                        } else {
                            lastFlickDuration = _getCurrentTime() - _gestureStartTime; // total gesture duration
                            tempReleasePos = _startPoint[axis];
                        }
                        s.lastFlickOffset[axis] = _currPoint[axis] - tempReleasePos;
                        s.lastFlickDist[axis] = Math.abs(s.lastFlickOffset[axis]);
                        if (s.lastFlickDist[axis] > 20) {
                            s.lastFlickSpeed[axis] = s.lastFlickOffset[axis] / lastFlickDuration;
                        } else {
                            s.lastFlickSpeed[axis] = 0;
                        }
                        if (Math.abs(s.lastFlickSpeed[axis]) < 0.1) {
                            s.lastFlickSpeed[axis] = 0;
                        }

                        s.slowDownRatio[axis] = 0.95;
                        s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
                        s.speedDecelerationRatio[axis] = 1;
                    },

                    calculateOverBoundsAnimOffset: function (axis, speed) {
                        if (!s.backAnimStarted[axis]) {

                            if (_panOffset[axis] > _currPanBounds.min[axis]) {
                                s.backAnimDestination[axis] = _currPanBounds.min[axis];

                            } else if (_panOffset[axis] < _currPanBounds.max[axis]) {
                                s.backAnimDestination[axis] = _currPanBounds.max[axis];
                            }

                            if (s.backAnimDestination[axis] !== undefined) {
                                s.slowDownRatio[axis] = 0.7;
                                s.slowDownRatioReverse[axis] = 1 - s.slowDownRatio[axis];
                                if (s.speedDecelerationRatioAbs[axis] < 0.05) {

                                    s.lastFlickSpeed[axis] = 0;
                                    s.backAnimStarted[axis] = true;

                                    _animateProp('bounceZoomPan' + axis, _panOffset[axis],
                                        s.backAnimDestination[axis],
                                        speed || 300,
                                        framework.easing.sine.out,
                                        function (pos) {
                                            _panOffset[axis] = pos;
                                            _applyCurrentZoomPan();
                                        }
                                    );

                                }
                            }
                        }
                    },

                    // Reduces the speed by slowDownRatio (per 10ms)
                    calculateAnimOffset: function (axis) {
                        if (!s.backAnimStarted[axis]) {
                            s.speedDecelerationRatio[axis] = s.speedDecelerationRatio[axis] * (s.slowDownRatio[axis] +
                                s.slowDownRatioReverse[axis] -
                                s.slowDownRatioReverse[axis] * s.timeDiff / 10);

                            s.speedDecelerationRatioAbs[axis] = Math.abs(s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis]);
                            s.distanceOffset[axis] = s.lastFlickSpeed[axis] * s.speedDecelerationRatio[axis] * s.timeDiff;
                            _panOffset[axis] += s.distanceOffset[axis];

                        }
                    },

                    panAnimLoop: function () {
                        if (_animations.zoomPan) {
                            _animations.zoomPan.raf = _requestAF(s.panAnimLoop);

                            s.now = _getCurrentTime();
                            s.timeDiff = s.now - s.lastNow;
                            s.lastNow = s.now;

                            s.calculateAnimOffset('x');
                            s.calculateAnimOffset('y');

                            _applyCurrentZoomPan();

                            s.calculateOverBoundsAnimOffset('x');
                            s.calculateOverBoundsAnimOffset('y');


                            if (s.speedDecelerationRatioAbs.x < 0.05 && s.speedDecelerationRatioAbs.y < 0.05) {

                                // round pan position
                                _panOffset.x = Math.round(_panOffset.x);
                                _panOffset.y = Math.round(_panOffset.y);
                                _applyCurrentZoomPan();

                                _stopAnimation('zoomPan');
                                return;
                            }
                        }

                    }
                };
                return s;
            },

            _completePanGesture = function (animData) {
                // calculate swipe speed for Y axis (paanning)
                animData.calculateSwipeSpeed('y');

                _currPanBounds = self.currItem.bounds;

                animData.backAnimDestination = {};
                animData.backAnimStarted = {};

                // Avoid acceleration animation if speed is too low
                if (Math.abs(animData.lastFlickSpeed.x) <= 0.05 && Math.abs(animData.lastFlickSpeed.y) <= 0.05) {
                    animData.speedDecelerationRatioAbs.x = animData.speedDecelerationRatioAbs.y = 0;

                    // Run pan drag release animation. E.g. if you drag image and release finger without momentum.
                    animData.calculateOverBoundsAnimOffset('x');
                    animData.calculateOverBoundsAnimOffset('y');
                    return true;
                }

                // Animation loop that controls the acceleration after pan gesture ends
                _registerStartAnimation('zoomPan');
                animData.lastNow = _getCurrentTime();
                animData.panAnimLoop();
            },


            _finishSwipeMainScrollGesture = function (gestureType, _releaseAnimData) {
                var itemChanged;
                if (!_mainScrollAnimating) {
                    _currZoomedItemIndex = _currentItemIndex;
                }



                var itemsDiff;

                if (gestureType === 'swipe') {
                    var totalShiftDist = _currPoint.x - _startPoint.x,
                        isFastLastFlick = _releaseAnimData.lastFlickDist.x < 10;

                    // if container is shifted for more than MIN_SWIPE_DISTANCE, 
                    // and last flick gesture was in right direction
                    if (totalShiftDist > MIN_SWIPE_DISTANCE &&
                        (isFastLastFlick || _releaseAnimData.lastFlickOffset.x > 20)) {
                        // go to prev item
                        itemsDiff = -1;
                    } else if (totalShiftDist < -MIN_SWIPE_DISTANCE &&
                        (isFastLastFlick || _releaseAnimData.lastFlickOffset.x < -20)) {
                        // go to next item
                        itemsDiff = 1;
                    }
                }

                var nextCircle;

                if (itemsDiff) {

                    _currentItemIndex += itemsDiff;

                    if (_currentItemIndex < 0) {
                        _currentItemIndex = _options.loop ? _getNumItems() - 1 : 0;
                        nextCircle = true;
                    } else if (_currentItemIndex >= _getNumItems()) {
                        _currentItemIndex = _options.loop ? 0 : _getNumItems() - 1;
                        nextCircle = true;
                    }

                    if (!nextCircle || _options.loop) {
                        _indexDiff += itemsDiff;
                        _currPositionIndex -= itemsDiff;
                        itemChanged = true;
                    }



                }

                var animateToX = _slideSize.x * _currPositionIndex;
                var animateToDist = Math.abs(animateToX - _mainScrollPos.x);
                var finishAnimDuration;


                if (!itemChanged && animateToX > _mainScrollPos.x !== _releaseAnimData.lastFlickSpeed.x > 0) {
                    // "return to current" duration, e.g. when dragging from slide 0 to -1
                    finishAnimDuration = 333;
                } else {
                    finishAnimDuration = Math.abs(_releaseAnimData.lastFlickSpeed.x) > 0 ?
                        animateToDist / Math.abs(_releaseAnimData.lastFlickSpeed.x) :
                        333;

                    finishAnimDuration = Math.min(finishAnimDuration, 400);
                    finishAnimDuration = Math.max(finishAnimDuration, 250);
                }

                if (_currZoomedItemIndex === _currentItemIndex) {
                    itemChanged = false;
                }

                _mainScrollAnimating = true;

                _shout('mainScrollAnimStart');

                _animateProp('mainScroll', _mainScrollPos.x, animateToX, finishAnimDuration, framework.easing.cubic.out,
                    _moveMainScroll,
                    function () {
                        _stopAllAnimations();
                        _mainScrollAnimating = false;
                        _currZoomedItemIndex = -1;

                        if (itemChanged || _currZoomedItemIndex !== _currentItemIndex) {
                            self.updateCurrItem();
                        }

                        _shout('mainScrollAnimComplete');
                    }
                );

                if (itemChanged) {
                    self.updateCurrItem(true);
                }

                return itemChanged;
            },

            _calculateZoomLevel = function (touchesDistance) {
                return 1 / _startPointsDistance * touchesDistance * _startZoomLevel;
            },

            // Resets zoom if it's out of bounds
            _completeZoomGesture = function () {
                var destZoomLevel = _currZoomLevel,
                    minZoomLevel = _getMinZoomLevel(),
                    maxZoomLevel = _getMaxZoomLevel();

                if (_currZoomLevel < minZoomLevel) {
                    destZoomLevel = minZoomLevel;
                } else if (_currZoomLevel > maxZoomLevel) {
                    destZoomLevel = maxZoomLevel;
                }

                var destOpacity = 1,
                    onUpdate,
                    initialOpacity = _bgOpacity;

                if (_opacityChanged && !_isZoomingIn && !_wasOverInitialZoom && _currZoomLevel < minZoomLevel) {
                    //_closedByScroll = true;
                    self.close();
                    return true;
                }

                if (_opacityChanged) {
                    onUpdate = function (now) {
                        _applyBgOpacity((destOpacity - initialOpacity) * now + initialOpacity);
                    };
                }

                self.zoomTo(destZoomLevel, 0, 200, framework.easing.cubic.out, onUpdate);
                return true;
            };


        _registerModule('Gestures', {
            publicMethods: {

                initGestures: function () {

                    // helper function that builds touch/pointer/mouse events
                    var addEventNames = function (pref, down, move, up, cancel) {
                        _dragStartEvent = pref + down;
                        _dragMoveEvent = pref + move;
                        _dragEndEvent = pref + up;
                        if (cancel) {
                            _dragCancelEvent = pref + cancel;
                        } else {
                            _dragCancelEvent = '';
                        }
                    };

                    _pointerEventEnabled = _features.pointerEvent;
                    if (_pointerEventEnabled && _features.touch) {
                        // we don't need touch events, if browser supports pointer events
                        _features.touch = false;
                    }

                    if (_pointerEventEnabled) {
                        if (navigator.pointerEnabled) {
                            addEventNames('pointer', 'down', 'move', 'up', 'cancel');
                        } else {
                            // IE10 pointer events are case-sensitive
                            addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
                        }
                    } else if (_features.touch) {
                        addEventNames('touch', 'start', 'move', 'end', 'cancel');
                        _likelyTouchDevice = true;
                    } else {
                        addEventNames('mouse', 'down', 'move', 'up');
                    }

                    _upMoveEvents = _dragMoveEvent + ' ' + _dragEndEvent + ' ' + _dragCancelEvent;
                    _downEvents = _dragStartEvent;

                    if (_pointerEventEnabled && !_likelyTouchDevice) {
                        _likelyTouchDevice = (navigator.maxTouchPoints > 1) || (navigator.msMaxTouchPoints > 1);
                    }
                    // make variable public
                    self.likelyTouchDevice = _likelyTouchDevice;

                    _globalEventHandlers[_dragStartEvent] = _onDragStart;
                    _globalEventHandlers[_dragMoveEvent] = _onDragMove;
                    _globalEventHandlers[_dragEndEvent] = _onDragRelease; // the Kraken

                    if (_dragCancelEvent) {
                        _globalEventHandlers[_dragCancelEvent] = _globalEventHandlers[_dragEndEvent];
                    }

                    // Bind mouse events on device with detected hardware touch support, in case it supports multiple types of input.
                    if (_features.touch) {
                        _downEvents += ' mousedown';
                        _upMoveEvents += ' mousemove mouseup';
                        _globalEventHandlers.mousedown = _globalEventHandlers[_dragStartEvent];
                        _globalEventHandlers.mousemove = _globalEventHandlers[_dragMoveEvent];
                        _globalEventHandlers.mouseup = _globalEventHandlers[_dragEndEvent];
                    }

                    if (!_likelyTouchDevice) {
                        // don't allow pan to next slide from zoomed state on Desktop
                        _options.allowPanToNext = false;
                    }
                }

            }
        });


        /*>>gestures*/

        /*>>show-hide-transition*/
        /**
         * show-hide-transition.js:
         *
         * Manages initial opening or closing transition.
         *
         * If you're not planning to use transition for gallery at all,
         * you may set options hideAnimationDuration and showAnimationDuration to 0,
         * and just delete startAnimation function.
         * 
         */


        var _showOrHideTimeout,
            _showOrHide = function (item, img, out, completeFn) {

                if (_showOrHideTimeout) {
                    clearTimeout(_showOrHideTimeout);
                }

                _initialZoomRunning = true;
                _initialContentSet = true;

                // dimensions of small thumbnail {x:,y:,w:}.
                // Height is optional, as calculated based on large image.
                var thumbBounds;
                if (item.initialLayout) {
                    thumbBounds = item.initialLayout;
                    item.initialLayout = null;
                } else {
                    thumbBounds = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
                }

                var duration = out ? _options.hideAnimationDuration : _options.showAnimationDuration;

                var onComplete = function () {
                    _stopAnimation('initialZoom');
                    if (!out) {
                        _applyBgOpacity(1);
                        if (img) {
                            img.style.display = 'block';
                        }
                        framework.addClass(template, 'pswp--animated-in');
                        _shout('initialZoom' + (out ? 'OutEnd' : 'InEnd'));
                    } else {
                        self.template.removeAttribute('style');
                        self.bg.removeAttribute('style');
                    }

                    if (completeFn) {
                        completeFn();
                    }
                    _initialZoomRunning = false;
                };

                // if bounds aren't provided, just open gallery without animation
                if (!duration || !thumbBounds || thumbBounds.x === undefined) {

                    _shout('initialZoom' + (out ? 'Out' : 'In'));

                    _currZoomLevel = item.initialZoomLevel;
                    _equalizePoints(_panOffset, item.initialPosition);
                    _applyCurrentZoomPan();

                    template.style.opacity = out ? 0 : 1;
                    _applyBgOpacity(1);

                    if (duration) {
                        setTimeout(function () {
                            onComplete();
                        }, duration);
                    } else {
                        onComplete();
                    }

                    return;
                }

                var startAnimation = function () {
                    var closeWithRaf = _closedByScroll,
                        fadeEverything = !self.currItem.src || self.currItem.loadError || _options.showHideOpacity;

                    // apply hw-acceleration to image
                    if (item.miniImg) {
                        item.miniImg.style.webkitBackfaceVisibility = 'hidden';
                    }

                    if (!out) {
                        _currZoomLevel = thumbBounds.w / item.w;
                        _panOffset.x = thumbBounds.x;
                        _panOffset.y = thumbBounds.y - _initalWindowScrollY;

                        self[fadeEverything ? 'template' : 'bg'].style.opacity = 0.001;
                        _applyCurrentZoomPan();
                    }

                    _registerStartAnimation('initialZoom');

                    if (out && !closeWithRaf) {
                        framework.removeClass(template, 'pswp--animated-in');
                    }

                    if (fadeEverything) {
                        if (out) {
                            framework[(closeWithRaf ? 'remove' : 'add') + 'Class'](template, 'pswp--animate_opacity');
                        } else {
                            setTimeout(function () {
                                framework.addClass(template, 'pswp--animate_opacity');
                            }, 30);
                        }
                    }

                    _showOrHideTimeout = setTimeout(function () {

                        _shout('initialZoom' + (out ? 'Out' : 'In'));


                        if (!out) {

                            // "in" animation always uses CSS transitions (instead of rAF).
                            // CSS transition work faster here, 
                            // as developer may also want to animate other things, 
                            // like ui on top of sliding area, which can be animated just via CSS

                            _currZoomLevel = item.initialZoomLevel;
                            _equalizePoints(_panOffset, item.initialPosition);
                            _applyCurrentZoomPan();
                            _applyBgOpacity(1);

                            if (fadeEverything) {
                                template.style.opacity = 1;
                            } else {
                                _applyBgOpacity(1);
                            }

                            _showOrHideTimeout = setTimeout(onComplete, duration + 20);
                        } else {

                            // "out" animation uses rAF only when PhotoSwipe is closed by browser scroll, to recalculate position
                            var destZoomLevel = thumbBounds.w / item.w,
                                initialPanOffset = {
                                    x: _panOffset.x,
                                    y: _panOffset.y
                                },
                                initialZoomLevel = _currZoomLevel,
                                initalBgOpacity = _bgOpacity,
                                onUpdate = function (now) {

                                    if (now === 1) {
                                        _currZoomLevel = destZoomLevel;
                                        _panOffset.x = thumbBounds.x;
                                        _panOffset.y = thumbBounds.y - _currentWindowScrollY;
                                    } else {
                                        _currZoomLevel = (destZoomLevel - initialZoomLevel) * now + initialZoomLevel;
                                        _panOffset.x = (thumbBounds.x - initialPanOffset.x) * now + initialPanOffset.x;
                                        _panOffset.y = (thumbBounds.y - _currentWindowScrollY - initialPanOffset.y) * now + initialPanOffset.y;
                                    }

                                    _applyCurrentZoomPan();
                                    if (fadeEverything) {
                                        template.style.opacity = 1 - now;
                                    } else {
                                        _applyBgOpacity(initalBgOpacity - now * initalBgOpacity);
                                    }
                                };

                            if (closeWithRaf) {
                                _animateProp('initialZoom', 0, 1, duration, framework.easing.cubic.out, onUpdate, onComplete);
                            } else {
                                onUpdate(1);
                                _showOrHideTimeout = setTimeout(onComplete, duration + 20);
                            }
                        }

                    }, out ? 25 : 90); // Main purpose of this delay is to give browser time to paint and
                    // create composite layers of PhotoSwipe UI parts (background, controls, caption, arrows).
                    // Which avoids lag at the beginning of scale transition.
                };
                startAnimation();


            };

        /*>>show-hide-transition*/

        /*>>items-controller*/
        /**
        *
        * Controller manages gallery items, their dimensions, and their content.
        * 
        */

        var _items,
            _tempPanAreaSize = {},
            _imagesToAppendPool = [],
            _initialContentSet,
            _initialZoomRunning,
            _controllerDefaultOptions = {
                index: 0,
                errorMsg: '<div class="pswp__error-msg"><a href="%url%" target="_blank">The image</a> could not be loaded.</div>',
                forceProgressiveLoading: false, // TODO
                preload: [1, 1],
                getNumItemsFn: function () {
                    return _items.length;
                }
            };


        var _getItemAt,
            _getNumItems,
            _initialIsLoop,
            _getZeroBounds = function () {
                return {
                    center: { x: 0, y: 0 },
                    max: { x: 0, y: 0 },
                    min: { x: 0, y: 0 }
                };
            },
            _calculateSingleItemPanBounds = function (item, realPanElementW, realPanElementH) {
                var bounds = item.bounds;

                // position of element when it's centered
                bounds.center.x = Math.round((_tempPanAreaSize.x - realPanElementW) / 2);
                bounds.center.y = Math.round((_tempPanAreaSize.y - realPanElementH) / 2) + item.vGap.top;

                // maximum pan position
                bounds.max.x = (realPanElementW > _tempPanAreaSize.x) ?
                    Math.round(_tempPanAreaSize.x - realPanElementW) :
                    bounds.center.x;

                bounds.max.y = (realPanElementH > _tempPanAreaSize.y) ?
                    Math.round(_tempPanAreaSize.y - realPanElementH) + item.vGap.top :
                    bounds.center.y;

                // minimum pan position
                bounds.min.x = (realPanElementW > _tempPanAreaSize.x) ? 0 : bounds.center.x;
                bounds.min.y = (realPanElementH > _tempPanAreaSize.y) ? item.vGap.top : bounds.center.y;
            },
            _calculateItemSize = function (item, viewportSize, zoomLevel) {

                if (item.src && !item.loadError) {
                    var isInitial = !zoomLevel;

                    if (isInitial) {
                        if (!item.vGap) {
                            item.vGap = { top: 0, bottom: 0 };
                        }
                        // allows overriding vertical margin for individual items
                        _shout('parseVerticalMargin', item);
                    }


                    _tempPanAreaSize.x = viewportSize.x;
                    _tempPanAreaSize.y = viewportSize.y - item.vGap.top - item.vGap.bottom;

                    if (isInitial) {
                        var hRatio = _tempPanAreaSize.x / item.w;
                        var vRatio = _tempPanAreaSize.y / item.h;

                        item.fitRatio = hRatio < vRatio ? hRatio : vRatio;
                        //item.fillRatio = hRatio > vRatio ? hRatio : vRatio;

                        var scaleMode = _options.scaleMode;

                        if (scaleMode === 'orig') {
                            zoomLevel = 1;
                        } else if (scaleMode === 'fit') {
                            zoomLevel = item.fitRatio;
                        }

                        if (zoomLevel > 1) {
                            zoomLevel = 1;
                        }

                        item.initialZoomLevel = zoomLevel;

                        if (!item.bounds) {
                            // reuse bounds object
                            item.bounds = _getZeroBounds();
                        }
                    }

                    if (!zoomLevel) {
                        return;
                    }

                    _calculateSingleItemPanBounds(item, item.w * zoomLevel, item.h * zoomLevel);

                    if (isInitial && zoomLevel === item.initialZoomLevel) {
                        item.initialPosition = item.bounds.center;
                    }

                    return item.bounds;
                } else {
                    item.w = item.h = 0;
                    item.initialZoomLevel = item.fitRatio = 1;
                    item.bounds = _getZeroBounds();
                    item.initialPosition = item.bounds.center;

                    // if it's not image, we return zero bounds (content is not zoomable)
                    return item.bounds;
                }
                return false;
            },




            _appendImage = function (index, item, baseDiv, img, preventAnimation, keepPlaceholder) {


                if (item.loadError) {
                    return;
                }

                if (img) {

                    item.imageAppended = true;
                    _setImageSize(item, img);

                    baseDiv.appendChild(img);

                    if (keepPlaceholder) {
                        setTimeout(function () {
                            if (item && item.loaded && item.placeholder) {
                                item.placeholder.style.display = 'none';
                                item.placeholder = null;
                            }
                        }, 500);
                    }
                }
            },



            _preloadImage = function (item) {
                item.loading = true;
                item.loaded = false;
                var img = item.img = framework.createEl('pswp__img', 'img');
                var onComplete = function () {
                    item.loading = false;
                    item.loaded = true;

                    if (item.loadComplete) {
                        item.loadComplete(item);
                    } else {
                        item.img = null; // no need to store image object
                    }
                    img.onload = img.onerror = null;
                    img = null;
                };
                img.onload = onComplete;
                img.onerror = function () {
                    item.loadError = true;
                    onComplete();
                };

                img.src = item.src;// + '?a=' + Math.random();

                return img;
            },
            _checkForError = function (item, cleanUp) {
                if (item.src && item.loadError && item.container) {

                    if (cleanUp) {
                        item.container.innerHTML = '';
                    }

                    item.container.innerHTML = _options.errorMsg.replace('%url%', item.src);
                    return true;

                }
            },
            _setImageSize = function (item, img, maxRes) {
                if (!item.src) {
                    return;
                }

                if (!img) {
                    img = item.container.lastChild;
                }

                var w = maxRes ? item.w : Math.round(item.w * item.fitRatio),
                    h = maxRes ? item.h : Math.round(item.h * item.fitRatio);

                if (item.placeholder && !item.loaded) {
                    item.placeholder.style.width = w + 'px';
                    item.placeholder.style.height = h + 'px';
                }

                img.style.width = w + 'px';
                img.style.height = h + 'px';
            },
            _appendImagesPool = function () {

                if (_imagesToAppendPool.length) {
                    var poolItem;

                    for (var i = 0; i < _imagesToAppendPool.length; i++) {
                        poolItem = _imagesToAppendPool[i];
                        if (poolItem.holder.index === poolItem.index) {
                            _appendImage(poolItem.index, poolItem.item, poolItem.baseDiv, poolItem.img, false, poolItem.clearPlaceholder);
                        }
                    }
                    _imagesToAppendPool = [];
                }
            };



        _registerModule('Controller', {

            publicMethods: {

                lazyLoadItem: function (index) {
                    index = _getLoopedId(index);
                    var item = _getItemAt(index);

                    if (!item || ((item.loaded || item.loading) && !_itemsNeedUpdate)) {
                        return;
                    }

                    _shout('gettingData', index, item);

                    if (!item.src) {
                        return;
                    }

                    _preloadImage(item);
                },
                initController: function () {
                    framework.extend(_options, _controllerDefaultOptions, true);
                    self.items = _items = items;
                    _getItemAt = self.getItemAt;
                    _getNumItems = _options.getNumItemsFn; //self.getNumItems;



                    _initialIsLoop = _options.loop;
                    if (_getNumItems() < 3) {
                        _options.loop = false; // disable loop if less then 3 items
                    }

                    _listen('beforeChange', function (diff) {

                        var p = _options.preload,
                            isNext = diff === null ? true : (diff >= 0),
                            preloadBefore = Math.min(p[0], _getNumItems()),
                            preloadAfter = Math.min(p[1], _getNumItems()),
                            i;


                        for (i = 1; i <= (isNext ? preloadAfter : preloadBefore); i++) {
                            self.lazyLoadItem(_currentItemIndex + i);
                        }
                        for (i = 1; i <= (isNext ? preloadBefore : preloadAfter); i++) {
                            self.lazyLoadItem(_currentItemIndex - i);
                        }
                    });

                    _listen('initialLayout', function () {
                        self.currItem.initialLayout = _options.getThumbBoundsFn && _options.getThumbBoundsFn(_currentItemIndex);
                    });

                    _listen('mainScrollAnimComplete', _appendImagesPool);
                    _listen('initialZoomInEnd', _appendImagesPool);



                    _listen('destroy', function () {
                        var item;
                        for (var i = 0; i < _items.length; i++) {
                            item = _items[i];
                            // remove reference to DOM elements, for GC
                            if (item.container) {
                                item.container = null;
                            }
                            if (item.placeholder) {
                                item.placeholder = null;
                            }
                            if (item.img) {
                                item.img = null;
                            }
                            if (item.preloader) {
                                item.preloader = null;
                            }
                            if (item.loadError) {
                                item.loaded = item.loadError = false;
                            }
                        }
                        _imagesToAppendPool = null;
                    });
                },


                getItemAt: function (index) {
                    if (index >= 0) {
                        return _items[index] !== undefined ? _items[index] : false;
                    }
                    return false;
                },

                allowProgressiveImg: function () {
                    // 1. Progressive image loading isn't working on webkit/blink 
                    //    when hw-acceleration (e.g. translateZ) is applied to IMG element.
                    //    That's why in PhotoSwipe parent element gets zoom transform, not image itself.
                    //    
                    // 2. Progressive image loading sometimes blinks in webkit/blink when applying animation to parent element.
                    //    That's why it's disabled on touch devices (mainly because of swipe transition)
                    //    
                    // 3. Progressive image loading sometimes doesn't work in IE (up to 11).

                    // Don't allow progressive loading on non-large touch devices
                    return _options.forceProgressiveLoading || !_likelyTouchDevice || _options.mouseUsed || screen.width > 1200;
                    // 1200 - to eliminate touch devices with large screen (like Chromebook Pixel)
                },

                setContent: function (holder, index) {

                    if (_options.loop) {
                        index = _getLoopedId(index);
                    }

                    var prevItem = self.getItemAt(holder.index);
                    if (prevItem) {
                        prevItem.container = null;
                    }

                    var item = self.getItemAt(index),
                        img;

                    if (!item) {
                        holder.el.innerHTML = '';
                        return;
                    }

                    // allow to override data
                    _shout('gettingData', index, item);

                    holder.index = index;
                    holder.item = item;

                    // base container DIV is created only once for each of 3 holders
                    var baseDiv = item.container = framework.createEl('pswp__zoom-wrap');



                    if (!item.src && item.html) {
                        if (item.html.tagName) {
                            baseDiv.appendChild(item.html);
                        } else {
                            baseDiv.innerHTML = item.html;
                        }
                    }

                    _checkForError(item);

                    _calculateItemSize(item, _viewportSize);

                    if (item.src && !item.loadError && !item.loaded) {

                        item.loadComplete = function (item) {

                            // gallery closed before image finished loading
                            if (!_isOpen) {
                                return;
                            }

                            // check if holder hasn't changed while image was loading
                            if (holder && holder.index === index) {
                                if (_checkForError(item, true)) {
                                    item.loadComplete = item.img = null;
                                    _calculateItemSize(item, _viewportSize);
                                    _applyZoomPanToItem(item);

                                    if (holder.index === _currentItemIndex) {
                                        // recalculate dimensions
                                        self.updateCurrZoomItem();
                                    }
                                    return;
                                }
                                if (!item.imageAppended) {
                                    if (_features.transform && (_mainScrollAnimating || _initialZoomRunning)) {
                                        _imagesToAppendPool.push({
                                            item: item,
                                            baseDiv: baseDiv,
                                            img: item.img,
                                            index: index,
                                            holder: holder,
                                            clearPlaceholder: true
                                        });
                                    } else {
                                        _appendImage(index, item, baseDiv, item.img, _mainScrollAnimating || _initialZoomRunning, true);
                                    }
                                } else {
                                    // remove preloader & mini-img
                                    if (!_initialZoomRunning && item.placeholder) {
                                        item.placeholder.style.display = 'none';
                                        item.placeholder = null;
                                    }
                                }
                            }

                            item.loadComplete = null;
                            item.img = null; // no need to store image element after it's added

                            _shout('imageLoadComplete', index, item);
                        };

                        if (framework.features.transform) {

                            var placeholderClassName = 'pswp__img pswp__img--placeholder';
                            placeholderClassName += (item.msrc ? '' : ' pswp__img--placeholder--blank');

                            var placeholder = framework.createEl(placeholderClassName, item.msrc ? 'img' : '');
                            if (item.msrc) {
                                placeholder.src = item.msrc;
                            }

                            _setImageSize(item, placeholder);

                            baseDiv.appendChild(placeholder);
                            item.placeholder = placeholder;

                        }




                        if (!item.loading) {
                            _preloadImage(item);
                        }


                        if (self.allowProgressiveImg()) {
                            // just append image
                            if (!_initialContentSet && _features.transform) {
                                _imagesToAppendPool.push({
                                    item: item,
                                    baseDiv: baseDiv,
                                    img: item.img,
                                    index: index,
                                    holder: holder
                                });
                            } else {
                                _appendImage(index, item, baseDiv, item.img, true, true);
                            }
                        }

                    } else if (item.src && !item.loadError) {
                        // image object is created every time, due to bugs of image loading & delay when switching images
                        img = framework.createEl('pswp__img', 'img');
                        img.style.opacity = 1;
                        img.src = item.src;
                        _setImageSize(item, img);
                        _appendImage(index, item, baseDiv, img, true);
                    }


                    if (!_initialContentSet && index === _currentItemIndex) {
                        _currZoomElementStyle = baseDiv.style;
                        _showOrHide(item, (img || item.img));
                    } else {
                        _applyZoomPanToItem(item);
                    }

                    holder.el.innerHTML = '';
                    holder.el.appendChild(baseDiv);
                },

                cleanSlide: function (item) {
                    if (item.img) {
                        item.img.onload = item.img.onerror = null;
                    }
                    item.loaded = item.loading = item.img = item.imageAppended = false;
                }

            }
        });

        /*>>items-controller*/

        /*>>tap*/
        /**
         * tap.js:
         *
         * Displatches tap and double-tap events.
         * 
         */

        var tapTimer,
            tapReleasePoint = {},
            _dispatchTapEvent = function (origEvent, releasePoint, pointerType) {
                var e = document.createEvent('CustomEvent'),
                    eDetail = {
                        origEvent: origEvent,
                        target: origEvent.target,
                        releasePoint: releasePoint,
                        pointerType: pointerType || 'touch'
                    };

                e.initCustomEvent('pswpTap', true, true, eDetail);
                origEvent.target.dispatchEvent(e);
            };

        _registerModule('Tap', {
            publicMethods: {
                initTap: function () {
                    _listen('firstTouchStart', self.onTapStart);
                    _listen('touchRelease', self.onTapRelease);
                    _listen('destroy', function () {
                        tapReleasePoint = {};
                        tapTimer = null;
                    });
                },
                onTapStart: function (touchList) {
                    if (touchList.length > 1) {
                        clearTimeout(tapTimer);
                        tapTimer = null;
                    }
                },
                onTapRelease: function (e, releasePoint) {
                    if (!releasePoint) {
                        return;
                    }

                    if (!_moved && !_isMultitouch && !_numAnimations) {
                        var p0 = releasePoint;
                        if (tapTimer) {
                            clearTimeout(tapTimer);
                            tapTimer = null;

                            // Check if taped on the same place
                            if (_isNearbyPoints(p0, tapReleasePoint)) {
                                _shout('doubleTap', p0);
                                return;
                            }
                        }

                        if (releasePoint.type === 'mouse') {
                            _dispatchTapEvent(e, releasePoint, 'mouse');
                            return;
                        }

                        var clickedTagName = e.target.tagName.toUpperCase();
                        // avoid double tap delay on buttons and elements that have class pswp__single-tap
                        if (clickedTagName === 'BUTTON' || framework.hasClass(e.target, 'pswp__single-tap')) {
                            _dispatchTapEvent(e, releasePoint);
                            return;
                        }

                        _equalizePoints(tapReleasePoint, p0);

                        tapTimer = setTimeout(function () {
                            _dispatchTapEvent(e, releasePoint);
                            tapTimer = null;
                        }, 300);
                    }
                }
            }
        });

        /*>>tap*/

        /*>>desktop-zoom*/
        /**
         *
         * desktop-zoom.js:
         *
         * - Binds mousewheel event for paning zoomed image.
         * - Manages "dragging", "zoomed-in", "zoom-out" classes.
         *   (which are used for cursors and zoom icon)
         * - Adds toggleDesktopZoom function.
         * 
         */

        var _wheelDelta;

        _registerModule('DesktopZoom', {

            publicMethods: {

                initDesktopZoom: function () {

                    if (_oldIE) {
                        // no zoom for old IE (<=8)
                        return;
                    }

                    if (_likelyTouchDevice) {
                        // if detected hardware touch support, we wait until mouse is used,
                        // and only then apply desktop-zoom features
                        _listen('mouseUsed', function () {
                            self.setupDesktopZoom();
                        });
                    } else {
                        self.setupDesktopZoom(true);
                    }

                },

                setupDesktopZoom: function (onInit) {

                    _wheelDelta = {};

                    var events = 'wheel mousewheel DOMMouseScroll';

                    _listen('bindEvents', function () {
                        framework.bind(template, events, self.handleMouseWheel);
                    });

                    _listen('unbindEvents', function () {
                        if (_wheelDelta) {
                            framework.unbind(template, events, self.handleMouseWheel);
                        }
                    });

                    self.mouseZoomedIn = false;

                    var hasDraggingClass,
                        updateZoomable = function () {
                            if (self.mouseZoomedIn) {
                                framework.removeClass(template, 'pswp--zoomed-in');
                                self.mouseZoomedIn = false;
                            }
                            if (_currZoomLevel < 1) {
                                framework.addClass(template, 'pswp--zoom-allowed');
                            } else {
                                framework.removeClass(template, 'pswp--zoom-allowed');
                            }
                            removeDraggingClass();
                        },
                        removeDraggingClass = function () {
                            if (hasDraggingClass) {
                                framework.removeClass(template, 'pswp--dragging');
                                hasDraggingClass = false;
                            }
                        };

                    _listen('resize', updateZoomable);
                    _listen('afterChange', updateZoomable);
                    _listen('pointerDown', function () {
                        if (self.mouseZoomedIn) {
                            hasDraggingClass = true;
                            framework.addClass(template, 'pswp--dragging');
                        }
                    });
                    _listen('pointerUp', removeDraggingClass);

                    if (!onInit) {
                        updateZoomable();
                    }

                },

                handleMouseWheel: function (e) {

                    if (_currZoomLevel <= self.currItem.fitRatio) {
                        if (_options.modal) {

                            if (!_options.closeOnScroll || _numAnimations || _isDragging) {
                                e.preventDefault();
                            } else if (_transformKey && Math.abs(e.deltaY) > 2) {
                                // close PhotoSwipe
                                // if browser supports transforms & scroll changed enough
                                _closedByScroll = true;
                                self.close();
                            }

                        }
                        return true;
                    }

                    // allow just one event to fire
                    e.stopPropagation();

                    // https://developer.mozilla.org/en-US/docs/Web/Events/wheel
                    _wheelDelta.x = 0;

                    if ('deltaX' in e) {
                        if (e.deltaMode === 1 /* DOM_DELTA_LINE */) {
                            // 18 - average line height
                            _wheelDelta.x = e.deltaX * 18;
                            _wheelDelta.y = e.deltaY * 18;
                        } else {
                            _wheelDelta.x = e.deltaX;
                            _wheelDelta.y = e.deltaY;
                        }
                    } else if ('wheelDelta' in e) {
                        if (e.wheelDeltaX) {
                            _wheelDelta.x = -0.16 * e.wheelDeltaX;
                        }
                        if (e.wheelDeltaY) {
                            _wheelDelta.y = -0.16 * e.wheelDeltaY;
                        } else {
                            _wheelDelta.y = -0.16 * e.wheelDelta;
                        }
                    } else if ('detail' in e) {
                        _wheelDelta.y = e.detail;
                    } else {
                        return;
                    }

                    _calculatePanBounds(_currZoomLevel, true);

                    var newPanX = _panOffset.x - _wheelDelta.x,
                        newPanY = _panOffset.y - _wheelDelta.y;

                    // only prevent scrolling in nonmodal mode when not at edges
                    if (_options.modal ||
                        (
                            newPanX <= _currPanBounds.min.x && newPanX >= _currPanBounds.max.x &&
                            newPanY <= _currPanBounds.min.y && newPanY >= _currPanBounds.max.y
                        )) {
                        e.preventDefault();
                    }

                    // TODO: use rAF instead of mousewheel?
                    self.panTo(newPanX, newPanY);
                },

                toggleDesktopZoom: function (centerPoint) {
                    centerPoint = centerPoint || { x: _viewportSize.x / 2 + _offset.x, y: _viewportSize.y / 2 + _offset.y };

                    var doubleTapZoomLevel = _options.getDoubleTapZoom(true, self.currItem);
                    var zoomOut = _currZoomLevel === doubleTapZoomLevel;

                    self.mouseZoomedIn = !zoomOut;

                    self.zoomTo(zoomOut ? self.currItem.initialZoomLevel : doubleTapZoomLevel, centerPoint, 333);
                    framework[(!zoomOut ? 'add' : 'remove') + 'Class'](template, 'pswp--zoomed-in');
                }

            }
        });


        /*>>desktop-zoom*/

        framework.extend(self, publicMethods);
    };
    return PhotoSwipe;
});

/*! PhotoSwipe Default UI - 4.1.0 - 2015-07-11
* http://photoswipe.com
* Copyright (c) 2015 Dmitry Semenov; */
/**
*
* UI on top of main sliding area (caption, arrows, close button, etc.).
* Built just using public methods/properties of PhotoSwipe.
* 
*/
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof exports === 'object') {
        module.exports = factory();
    } else {
        root.PhotoSwipeUI_Default = factory();
    }
})(this, function () {

    'use strict';



    var PhotoSwipeUI_Default =
        function (pswp, framework) {

            var ui = this;
            var _overlayUIUpdated = false,
                _controlsVisible = true,
                _fullscrenAPI,
                _controls,
                _captionContainer,
                _fakeCaptionContainer,
                _indexIndicator,
                _shareButton,
                _shareModal,
                _shareModalHidden = true,
                _initalCloseOnScrollValue,
                _isIdle,
                _listen,

                _loadingIndicator,
                _loadingIndicatorHidden,
                _loadingIndicatorTimeout,

                _galleryHasOneSlide,

                _options,
                _defaultUIOptions = {
                    barsSize: { top: 44, bottom: 'auto' },
                    closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'],
                    timeToIdle: 4000,
                    timeToIdleOutside: 1000,
                    loadingIndicatorDelay: 1000, // 2s

                    addCaptionHTMLFn: function (item, captionEl /*, isFake */) {
                        if (!item.title) {
                            captionEl.children[0].innerHTML = '';
                            return false;
                        }
                        captionEl.children[0].innerHTML = item.title;
                        return true;
                    },

                    closeEl: true,
                    captionEl: true,
                    fullscreenEl: true,
                    zoomEl: true,
                    shareEl: true,
                    counterEl: true,
                    arrowEl: true,
                    preloaderEl: true,

                    tapToClose: false,
                    tapToToggleControls: true,

                    clickToCloseNonZoomable: true,

                    shareButtons: [
                        { id: 'facebook', label: 'Share on Facebook', url: 'https://www.facebook.com/sharer/sharer.php?u={{url}}' },
                        { id: 'twitter', label: 'Tweet', url: 'https://x.com/intent/tweet?text={{text}}&url={{url}}' },
                        {
                            id: 'pinterest', label: 'Pin it', url: 'http://www.pinterest.com/pin/create/button/' +
                                '?url={{url}}&media={{image_url}}&description={{text}}'
                        },
                        { id: 'download', label: 'Download image', url: '{{raw_image_url}}', download: true }
                    ],
                    getImageURLForShare: function ( /* shareButtonData */) {
                        return pswp.currItem.src || '';
                    },
                    getPageURLForShare: function ( /* shareButtonData */) {
                        return window.location.href;
                    },
                    getTextForShare: function ( /* shareButtonData */) {
                        return pswp.currItem.title || '';
                    },

                    indexIndicatorSep: ' / '

                },
                _blockControlsTap,
                _blockControlsTapTimeout;



            var _onControlsTap = function (e) {
                if (_blockControlsTap) {
                    return true;
                }


                e = e || window.event;

                if (_options.timeToIdle && _options.mouseUsed && !_isIdle) {
                    // reset idle timer
                    _onIdleMouseMove();
                }


                var target = e.target || e.srcElement,
                    uiElement,
                    clickedClass = target.className,
                    found;

                for (var i = 0; i < _uiElements.length; i++) {
                    uiElement = _uiElements[i];
                    if (uiElement.onTap && clickedClass.indexOf('pswp__' + uiElement.name) > -1) {
                        uiElement.onTap();
                        found = true;

                    }
                }

                if (found) {
                    if (e.stopPropagation) {
                        e.stopPropagation();
                    }
                    _blockControlsTap = true;

                    // Some versions of Android don't prevent ghost click event 
                    // when preventDefault() was called on touchstart and/or touchend.
                    // 
                    // This happens on v4.3, 4.2, 4.1, 
                    // older versions strangely work correctly, 
                    // but just in case we add delay on all of them)	
                    var tapDelay = framework.features.isOldAndroid ? 600 : 30;
                    _blockControlsTapTimeout = setTimeout(function () {
                        _blockControlsTap = false;
                    }, tapDelay);
                }

            },
                _fitControlsInViewport = function () {
                    return !pswp.likelyTouchDevice || _options.mouseUsed || screen.width > 1200;
                },
                _togglePswpClass = function (el, cName, add) {
                    framework[(add ? 'add' : 'remove') + 'Class'](el, 'pswp__' + cName);
                },

                // add class when there is just one item in the gallery
                // (by default it hides left/right arrows and 1ofX counter)
                _countNumItems = function () {
                    var hasOneSlide = (_options.getNumItemsFn() === 1);

                    if (hasOneSlide !== _galleryHasOneSlide) {
                        _togglePswpClass(_controls, 'ui--one-slide', hasOneSlide);
                        _galleryHasOneSlide = hasOneSlide;
                    }
                },
                _toggleShareModalClass = function () {
                    _togglePswpClass(_shareModal, 'share-modal--hidden', _shareModalHidden);
                },
                _toggleShareModal = function () {

                    _shareModalHidden = !_shareModalHidden;


                    if (!_shareModalHidden) {
                        _toggleShareModalClass();
                        setTimeout(function () {
                            if (!_shareModalHidden) {
                                framework.addClass(_shareModal, 'pswp__share-modal--fade-in');
                            }
                        }, 30);
                    } else {
                        framework.removeClass(_shareModal, 'pswp__share-modal--fade-in');
                        setTimeout(function () {
                            if (_shareModalHidden) {
                                _toggleShareModalClass();
                            }
                        }, 300);
                    }

                    if (!_shareModalHidden) {
                        _updateShareURLs();
                    }
                    return false;
                },

                _openWindowPopup = function (e) {
                    e = e || window.event;
                    var target = e.target || e.srcElement;

                    pswp.shout('shareLinkClick', e, target);

                    if (!target.href) {
                        return false;
                    }

                    if (target.hasAttribute('download')) {
                        return true;
                    }

                    window.open(target.href, 'pswp_share', 'scrollbars=yes,resizable=yes,toolbar=no,' +
                        'location=yes,width=550,height=420,top=100,left=' +
                        (window.screen ? Math.round(screen.width / 2 - 275) : 100));

                    if (!_shareModalHidden) {
                        _toggleShareModal();
                    }

                    return false;
                },
                _updateShareURLs = function () {
                    var shareButtonOut = '',
                        shareButtonData,
                        shareURL,
                        image_url,
                        page_url,
                        share_text;

                    for (var i = 0; i < _options.shareButtons.length; i++) {
                        shareButtonData = _options.shareButtons[i];

                        image_url = _options.getImageURLForShare(shareButtonData);
                        page_url = _options.getPageURLForShare(shareButtonData);
                        share_text = _options.getTextForShare(shareButtonData);

                        shareURL = shareButtonData.url.replace('{{url}}', encodeURIComponent(page_url))
                            .replace('{{image_url}}', encodeURIComponent(image_url))
                            .replace('{{raw_image_url}}', image_url)
                            .replace('{{text}}', encodeURIComponent(share_text));

                        shareButtonOut += '<a href="' + shareURL + '" target="_blank" ' +
                            'class="pswp__share--' + shareButtonData.id + '"' +
                            (shareButtonData.download ? 'download' : '') + '>' +
                            shareButtonData.label + '</a>';

                        if (_options.parseShareButtonOut) {
                            shareButtonOut = _options.parseShareButtonOut(shareButtonData, shareButtonOut);
                        }
                    }
                    _shareModal.children[0].innerHTML = shareButtonOut;
                    _shareModal.children[0].onclick = _openWindowPopup;

                },
                _hasCloseClass = function (target) {
                    for (var i = 0; i < _options.closeElClasses.length; i++) {
                        if (framework.hasClass(target, 'pswp__' + _options.closeElClasses[i])) {
                            return true;
                        }
                    }
                },
                _idleInterval,
                _idleTimer,
                _idleIncrement = 0,
                _onIdleMouseMove = function () {
                    clearTimeout(_idleTimer);
                    _idleIncrement = 0;
                    if (_isIdle) {
                        ui.setIdle(false);
                    }
                },
                _onMouseLeaveWindow = function (e) {
                    e = e ? e : window.event;
                    var from = e.relatedTarget || e.toElement;
                    if (!from || from.nodeName === 'HTML') {
                        clearTimeout(_idleTimer);
                        _idleTimer = setTimeout(function () {
                            ui.setIdle(true);
                        }, _options.timeToIdleOutside);
                    }
                },
                _setupFullscreenAPI = function () {
                    if (_options.fullscreenEl) {
                        if (!_fullscrenAPI) {
                            _fullscrenAPI = ui.getFullscreenAPI();
                        }
                        if (_fullscrenAPI) {
                            framework.bind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
                            ui.updateFullscreen();
                            framework.addClass(pswp.template, 'pswp--supports-fs');
                        } else {
                            framework.removeClass(pswp.template, 'pswp--supports-fs');
                        }
                    }
                },
                _setupLoadingIndicator = function () {
                    // Setup loading indicator
                    if (_options.preloaderEl) {

                        _toggleLoadingIndicator(true);

                        _listen('beforeChange', function () {

                            clearTimeout(_loadingIndicatorTimeout);

                            // display loading indicator with delay
                            _loadingIndicatorTimeout = setTimeout(function () {

                                if (pswp.currItem && pswp.currItem.loading) {

                                    if (!pswp.allowProgressiveImg() || (pswp.currItem.img && !pswp.currItem.img.naturalWidth)) {
                                        // show preloader if progressive loading is not enabled, 
                                        // or image width is not defined yet (because of slow connection)
                                        _toggleLoadingIndicator(false);
                                        // items-controller.js function allowProgressiveImg
                                    }

                                } else {
                                    _toggleLoadingIndicator(true); // hide preloader
                                }

                            }, _options.loadingIndicatorDelay);

                        });
                        _listen('imageLoadComplete', function (index, item) {
                            if (pswp.currItem === item) {
                                _toggleLoadingIndicator(true);
                            }
                        });

                    }
                },
                _toggleLoadingIndicator = function (hide) {
                    if (_loadingIndicatorHidden !== hide) {
                        _togglePswpClass(_loadingIndicator, 'preloader--active', !hide);
                        _loadingIndicatorHidden = hide;
                    }
                },
                _applyNavBarGaps = function (item) {
                    var gap = item.vGap;

                    if (_fitControlsInViewport()) {

                        var bars = _options.barsSize;
                        if (_options.captionEl && bars.bottom === 'auto') {
                            if (!_fakeCaptionContainer) {
                                _fakeCaptionContainer = framework.createEl('pswp__caption pswp__caption--fake');
                                _fakeCaptionContainer.appendChild(framework.createEl('pswp__caption__center'));
                                _controls.insertBefore(_fakeCaptionContainer, _captionContainer);
                                framework.addClass(_controls, 'pswp__ui--fit');
                            }
                            if (_options.addCaptionHTMLFn(item, _fakeCaptionContainer, true)) {

                                var captionSize = _fakeCaptionContainer.clientHeight;
                                gap.bottom = parseInt(captionSize, 10) || 44;
                            } else {
                                gap.bottom = bars.top; // if no caption, set size of bottom gap to size of top
                            }
                        } else {
                            gap.bottom = bars.bottom === 'auto' ? 0 : bars.bottom;
                        }

                        // height of top bar is static, no need to calculate it
                        gap.top = bars.top;
                    } else {
                        gap.top = gap.bottom = 0;
                    }
                },
                _setupIdle = function () {
                    // Hide controls when mouse is used
                    if (_options.timeToIdle) {
                        _listen('mouseUsed', function () {

                            framework.bind(document, 'mousemove', _onIdleMouseMove);
                            framework.bind(document, 'mouseout', _onMouseLeaveWindow);

                            _idleInterval = setInterval(function () {
                                _idleIncrement++;
                                if (_idleIncrement === 2) {
                                    ui.setIdle(true);
                                }
                            }, _options.timeToIdle / 2);
                        });
                    }
                },
                _setupHidingControlsDuringGestures = function () {

                    // Hide controls on vertical drag
                    _listen('onVerticalDrag', function (now) {
                        if (_controlsVisible && now < 0.95) {
                            ui.hideControls();
                        } else if (!_controlsVisible && now >= 0.95) {
                            ui.showControls();
                        }
                    });

                    // Hide controls when pinching to close
                    var pinchControlsHidden;
                    _listen('onPinchClose', function (now) {
                        if (_controlsVisible && now < 0.9) {
                            ui.hideControls();
                            pinchControlsHidden = true;
                        } else if (pinchControlsHidden && !_controlsVisible && now > 0.9) {
                            ui.showControls();
                        }
                    });

                    _listen('zoomGestureEnded', function () {
                        pinchControlsHidden = false;
                        if (pinchControlsHidden && !_controlsVisible) {
                            ui.showControls();
                        }
                    });

                };



            var _uiElements = [
                {
                    name: 'caption',
                    option: 'captionEl',
                    onInit: function (el) {
                        _captionContainer = el;
                    }
                },
                {
                    name: 'share-modal',
                    option: 'shareEl',
                    onInit: function (el) {
                        _shareModal = el;
                    },
                    onTap: function () {
                        _toggleShareModal();
                    }
                },
                {
                    name: 'button--share',
                    option: 'shareEl',
                    onInit: function (el) {
                        _shareButton = el;
                    },
                    onTap: function () {
                        _toggleShareModal();
                    }
                },
                {
                    name: 'button--zoom',
                    option: 'zoomEl',
                    onTap: pswp.toggleDesktopZoom
                },
                {
                    name: 'counter',
                    option: 'counterEl',
                    onInit: function (el) {
                        _indexIndicator = el;
                    }
                },
                {
                    name: 'button--close',
                    option: 'closeEl',
                    onTap: pswp.close
                },
                {
                    name: 'button--arrow--left',
                    option: 'arrowEl',
                    onTap: pswp.prev
                },
                {
                    name: 'button--arrow--right',
                    option: 'arrowEl',
                    onTap: pswp.next
                },
                {
                    name: 'button--fs',
                    option: 'fullscreenEl',
                    onTap: function () {
                        if (_fullscrenAPI.isFullscreen()) {
                            _fullscrenAPI.exit();
                        } else {
                            _fullscrenAPI.enter();
                        }
                    }
                },
                {
                    name: 'preloader',
                    option: 'preloaderEl',
                    onInit: function (el) {
                        _loadingIndicator = el;
                    }
                }

            ];

            var _setupUIElements = function () {
                var item,
                    classAttr,
                    uiElement;

                var loopThroughChildElements = function (sChildren) {
                    if (!sChildren) {
                        return;
                    }

                    var l = sChildren.length;
                    for (var i = 0; i < l; i++) {
                        item = sChildren[i];
                        classAttr = item.className;

                        for (var a = 0; a < _uiElements.length; a++) {
                            uiElement = _uiElements[a];

                            if (classAttr.indexOf('pswp__' + uiElement.name) > -1) {

                                if (_options[uiElement.option]) { // if element is not disabled from options

                                    framework.removeClass(item, 'pswp__element--disabled');
                                    if (uiElement.onInit) {
                                        uiElement.onInit(item);
                                    }

                                    //item.style.display = 'block';
                                } else {
                                    framework.addClass(item, 'pswp__element--disabled');
                                    //item.style.display = 'none';
                                }
                            }
                        }
                    }
                };
                loopThroughChildElements(_controls.children);

                var topBar = framework.getChildByClass(_controls, 'pswp__top-bar');
                if (topBar) {
                    loopThroughChildElements(topBar.children);
                }
            };




            ui.init = function () {

                // extend options
                framework.extend(pswp.options, _defaultUIOptions, true);

                // create local link for fast access
                _options = pswp.options;

                // find pswp__ui element
                _controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui');

                // create local link
                _listen = pswp.listen;


                _setupHidingControlsDuringGestures();

                // update controls when slides change
                _listen('beforeChange', ui.update);

                // toggle zoom on double-tap
                _listen('doubleTap', function (point) {
                    var initialZoomLevel = pswp.currItem.initialZoomLevel;
                    if (pswp.getZoomLevel() !== initialZoomLevel) {
                        pswp.zoomTo(initialZoomLevel, point, 333);
                    } else {
                        pswp.zoomTo(_options.getDoubleTapZoom(false, pswp.currItem), point, 333);
                    }
                });

                // Allow text selection in caption
                _listen('preventDragEvent', function (e, isDown, preventObj) {
                    var t = e.target || e.srcElement;
                    if (
                        t &&
                        t.className && e.type.indexOf('mouse') > -1 &&
                        (t.className.indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName))
                    ) {
                        preventObj.prevent = false;
                    }
                });

                // bind events for UI
                _listen('bindEvents', function () {
                    framework.bind(_controls, 'pswpTap click', _onControlsTap);
                    framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);

                    if (!pswp.likelyTouchDevice) {
                        framework.bind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);
                    }
                });

                // unbind events for UI
                _listen('unbindEvents', function () {
                    if (!_shareModalHidden) {
                        _toggleShareModal();
                    }

                    if (_idleInterval) {
                        clearInterval(_idleInterval);
                    }
                    framework.unbind(document, 'mouseout', _onMouseLeaveWindow);
                    framework.unbind(document, 'mousemove', _onIdleMouseMove);
                    framework.unbind(_controls, 'pswpTap click', _onControlsTap);
                    framework.unbind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
                    framework.unbind(pswp.scrollWrap, 'mouseover', ui.onMouseOver);

                    if (_fullscrenAPI) {
                        framework.unbind(document, _fullscrenAPI.eventK, ui.updateFullscreen);
                        if (_fullscrenAPI.isFullscreen()) {
                            _options.hideAnimationDuration = 0;
                            _fullscrenAPI.exit();
                        }
                        _fullscrenAPI = null;
                    }
                });


                // clean up things when gallery is destroyed
                _listen('destroy', function () {
                    if (_options.captionEl) {
                        if (_fakeCaptionContainer) {
                            _controls.removeChild(_fakeCaptionContainer);
                        }
                        framework.removeClass(_captionContainer, 'pswp__caption--empty');
                    }

                    if (_shareModal) {
                        _shareModal.children[0].onclick = null;
                    }
                    framework.removeClass(_controls, 'pswp__ui--over-close');
                    framework.addClass(_controls, 'pswp__ui--hidden');
                    ui.setIdle(false);
                });


                if (!_options.showAnimationDuration) {
                    framework.removeClass(_controls, 'pswp__ui--hidden');
                }
                _listen('initialZoomIn', function () {
                    if (_options.showAnimationDuration) {
                        framework.removeClass(_controls, 'pswp__ui--hidden');
                    }
                });
                _listen('initialZoomOut', function () {
                    framework.addClass(_controls, 'pswp__ui--hidden');
                });

                _listen('parseVerticalMargin', _applyNavBarGaps);

                _setupUIElements();

                if (_options.shareEl && _shareButton && _shareModal) {
                    _shareModalHidden = true;
                }

                _countNumItems();

                _setupIdle();

                _setupFullscreenAPI();

                _setupLoadingIndicator();
            };

            ui.setIdle = function (isIdle) {
                _isIdle = isIdle;
                _togglePswpClass(_controls, 'ui--idle', isIdle);
            };

            ui.update = function () {
                // Don't update UI if it's hidden
                if (_controlsVisible && pswp.currItem) {

                    ui.updateIndexIndicator();

                    if (_options.captionEl) {
                        _options.addCaptionHTMLFn(pswp.currItem, _captionContainer);

                        _togglePswpClass(_captionContainer, 'caption--empty', !pswp.currItem.title);
                    }

                    _overlayUIUpdated = true;

                } else {
                    _overlayUIUpdated = false;
                }

                if (!_shareModalHidden) {
                    _toggleShareModal();
                }

                _countNumItems();
            };

            ui.updateFullscreen = function (e) {

                if (e) {
                    // some browsers change window scroll position during the fullscreen
                    // so PhotoSwipe updates it just in case
                    setTimeout(function () {
                        pswp.setScrollOffset(0, framework.getScrollY());
                    }, 50);
                }

                // toogle pswp--fs class on root element
                framework[(_fullscrenAPI.isFullscreen() ? 'add' : 'remove') + 'Class'](pswp.template, 'pswp--fs');
            };

            ui.updateIndexIndicator = function () {
                if (_options.counterEl) {
                    _indexIndicator.innerHTML = (pswp.getCurrentIndex() + 1) +
                        _options.indexIndicatorSep +
                        _options.getNumItemsFn();
                }
            };

            ui.onGlobalTap = function (e) {
                e = e || window.event;
                var target = e.target || e.srcElement;

                if (_blockControlsTap) {
                    return;
                }

                if (e.detail && e.detail.pointerType === 'mouse') {

                    // close gallery if clicked outside of the image
                    if (_hasCloseClass(target)) {
                        pswp.close();
                        return;
                    }

                    if (framework.hasClass(target, 'pswp__img')) {
                        if (pswp.getZoomLevel() === 1 && pswp.getZoomLevel() <= pswp.currItem.fitRatio) {
                            if (_options.clickToCloseNonZoomable) {
                                pswp.close();
                            }
                        } else {
                            pswp.toggleDesktopZoom(e.detail.releasePoint);
                        }
                    }

                } else {

                    // tap anywhere (except buttons) to toggle visibility of controls
                    if (_options.tapToToggleControls) {
                        if (_controlsVisible) {
                            ui.hideControls();
                        } else {
                            ui.showControls();
                        }
                    }

                    // tap to close gallery
                    if (_options.tapToClose && (framework.hasClass(target, 'pswp__img') || _hasCloseClass(target))) {
                        pswp.close();
                        return;
                    }

                }
            };
            ui.onMouseOver = function (e) {
                e = e || window.event;
                var target = e.target || e.srcElement;

                // add class when mouse is over an element that should close the gallery
                _togglePswpClass(_controls, 'ui--over-close', _hasCloseClass(target));
            };

            ui.hideControls = function () {
                framework.addClass(_controls, 'pswp__ui--hidden');
                _controlsVisible = false;
            };

            ui.showControls = function () {
                _controlsVisible = true;
                if (!_overlayUIUpdated) {
                    ui.update();
                }
                framework.removeClass(_controls, 'pswp__ui--hidden');
            };

            ui.supportsFullscreen = function () {
                var d = document;
                return !!(d.exitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen || d.msExitFullscreen);
            };

            ui.getFullscreenAPI = function () {
                var dE = document.documentElement,
                    api,
                    tF = 'fullscreenchange';

                if (dE.requestFullscreen) {
                    api = {
                        enterK: 'requestFullscreen',
                        exitK: 'exitFullscreen',
                        elementK: 'fullscreenElement',
                        eventK: tF
                    };

                } else if (dE.mozRequestFullScreen) {
                    api = {
                        enterK: 'mozRequestFullScreen',
                        exitK: 'mozCancelFullScreen',
                        elementK: 'mozFullScreenElement',
                        eventK: 'moz' + tF
                    };



                } else if (dE.webkitRequestFullscreen) {
                    api = {
                        enterK: 'webkitRequestFullscreen',
                        exitK: 'webkitExitFullscreen',
                        elementK: 'webkitFullscreenElement',
                        eventK: 'webkit' + tF
                    };

                } else if (dE.msRequestFullscreen) {
                    api = {
                        enterK: 'msRequestFullscreen',
                        exitK: 'msExitFullscreen',
                        elementK: 'msFullscreenElement',
                        eventK: 'MSFullscreenChange'
                    };
                }

                if (api) {
                    api.enter = function () {
                        // disable close-on-scroll in fullscreen
                        _initalCloseOnScrollValue = _options.closeOnScroll;
                        _options.closeOnScroll = false;

                        if (this.enterK === 'webkitRequestFullscreen') {
                            pswp.template[this.enterK](Element.ALLOW_KEYBOARD_INPUT);
                        } else {
                            return pswp.template[this.enterK]();
                        }
                    };
                    api.exit = function () {
                        _options.closeOnScroll = _initalCloseOnScrollValue;

                        return document[this.exitK]();

                    };
                    api.isFullscreen = function () { return document[this.elementK]; };
                }

                return api;
            };



        };
    return PhotoSwipeUI_Default;


});


/*
Plugin: jQuery Parallax
Version 1.1.3
Author: Ian Lunn
Twitter: @IanLunn
Author URL: http://www.ianlunn.co.uk/
Plugin URL: http://www.ianlunn.co.uk/plugins/jquery-parallax/

Dual licensed under the MIT and GPL licenses:
http://www.opensource.org/licenses/mit-license.php
http://www.gnu.org/licenses/gpl.html
*/

(function( $ ){
	var $window = $(window);
	var windowHeight = $window.height();

	$window.on('resize', function () {
		windowHeight = $window.height();
	});

	$.fn.parallax = function(xpos, speedFactor, outerHeight) {
		var $this = $(this);
		var getHeight;
		var firstTop;
		var paddingTop = 0;
		
		//get the starting position of each element to have parallax applied to it		
		$this.each(function(){
		    firstTop = $this.offset().top;
		});

		if (outerHeight) {
			getHeight = function(jqo) {
				return jqo.outerHeight(true);
			};
		} else {
			getHeight = function(jqo) {
				return jqo.height();
			};
		}
			
		// setup defaults if arguments aren't specified
		if (arguments.length < 1 || xpos === null) xpos = "50%";
		if (arguments.length < 2 || speedFactor === null) speedFactor = 0.1;
		if (arguments.length < 3 || outerHeight === null) outerHeight = true;
		
		// function to be called whenever the window is scrolled or resized
		function update(){
			var pos = $window.scrollTop();

			if (!$this.hasClass('wd-parallax')){
				return;
			}

			$this.each(function(){
				var $element = $(this);
				var top = $element.offset().top;
				var height = getHeight($element);

				// Check if totally above or totally below viewport
				if (top + height < pos || top > pos + windowHeight) {
					return;
				}

				$this.attr('style', 'background-position: var(--wd-parallax-pos,' + xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px) !important");
			});
		}		

		$window.bind('scroll', update).on('resize', update);
		update();
	};
})(jQuery);
/*
 *
 * TERMS OF USE - EASING EQUATIONS
 * 
 * Open source under the BSD License. 
 * 
 * Copyright © 2001 Robert Penner
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
 */

 (function( $ ){
    $(function() {
        if ( $(window).width() <= 1024 || ( ! $('body').hasClass('elementor-editor-active') && 0 === $('.wd-parallax-on-scroll').length && 0 === $('[data-parallax]').length ) ) return;
        ParallaxScroll.init();
    });

    var ParallaxScroll = {
        /* PUBLIC VARIABLES */
        showLogs: false,
        round: 1000,

        /* PUBLIC FUNCTIONS */
        init: function() {
            this._log("init");
            if (this._inited) {
                this._log("Already Inited");
                this._inited = true;
                return;
            }
            this._requestAnimationFrame = (function(){
              return  window.requestAnimationFrame       || 
                      window.webkitRequestAnimationFrame || 
                      window.mozRequestAnimationFrame    || 
                      window.oRequestAnimationFrame      || 
                      window.msRequestAnimationFrame     || 
                      function(/* function */ callback, /* DOMElement */ element){
                          window.setTimeout(callback, 1000 / 60);
                      };
            })();
            this._onScroll(true);
        },

        /* PRIVATE VARIABLES */
        _inited: false,
        _properties: ['x', 'y', 'z', 'rotateX', 'rotateY', 'rotateZ', 'scaleX', 'scaleY', 'scaleZ', 'scale'],
        _requestAnimationFrame:null,

        /* PRIVATE FUNCTIONS */
        _log: function(message) {
            if (this.showLogs) console.log("Parallax Scroll / " + message);
        },
        _onScroll: function(noSmooth) {
            var scroll = $(document).scrollTop();
            var windowHeight = $(window).height();
            this._log("onScroll " + scroll);
            $("[data-parallax], .wd-parallax-on-scroll").each($.proxy(function(index, el) {
                var $el = $(el);
                var properties = [];
                var applyProperties = false;
                var style = $el.data("style");
                if (style == undefined) {
                    style = $el.attr("style") || "";
                    $el.data("style", style);
                }
                var datas;
                if (!$el.hasClass('wd-parallax-on-scroll')) {
                    datas = [$el.data("parallax")];
                } else {
                    var classes = $el.attr('class').split(' ');
                    datas = [[]];
                    for (var index = 0; index < classes.length; index++) {
                        if (classes[index].indexOf('wd_scroll') >= 0) {
                            var data = classes[index].split('_');
                            datas[0][data[2]] = data[3]
                        }
                    }
                }
                var iData;
                for(iData = 2; ; iData++) {
                    if($el.data("parallax"+iData)) {
                        datas.push($el.data("parallax-"+iData));
                    }
                    else {
                        break;
                    }
                }
                var datasLength = datas.length;
                for(iData = 0; iData < datasLength; iData ++) {
                    var data = datas[iData];
                    var scrollFrom = data["from-scroll"];
                    if (scrollFrom == undefined) scrollFrom = Math.max(0, $(el).offset().top - windowHeight);
                    scrollFrom = scrollFrom | 0;
                    var scrollDistance = data["distance"];
                    var scrollTo = data["to-scroll"];
                    if (scrollDistance == undefined && scrollTo == undefined) scrollDistance = windowHeight;
                    scrollDistance = Math.max(scrollDistance | 0, 1);
                    var easing = data["easing"];
                    var easingReturn = data["easing-return"];
                    if (easing == undefined || !$.easing|| !$.easing[easing]) easing = null;
                    if (easingReturn == undefined || !$.easing|| !$.easing[easingReturn]) easingReturn = easing;
                    if (easing) {
                        var totalTime = data["duration"];
                        if (totalTime == undefined) totalTime = scrollDistance;
                        totalTime = Math.max(totalTime | 0, 1);
                        var totalTimeReturn = data["duration-return"];
                        if (totalTimeReturn == undefined) totalTimeReturn = totalTime;
                        scrollDistance = 1;
                        var currentTime = $el.data("current-time");
                        if(currentTime == undefined) currentTime = 0;
                    }
                    if (scrollTo == undefined) scrollTo = scrollFrom + scrollDistance;
                    scrollTo = scrollTo | 0;
                    var smoothness = data["smoothness"];
                    if (smoothness == undefined) smoothness = 30;
                    smoothness = smoothness | 0;
                    if (noSmooth || smoothness == 0) smoothness = 1;
                    smoothness = smoothness | 0;
                    var scrollCurrent = scroll;
                    scrollCurrent = Math.max(scrollCurrent, scrollFrom);
                    scrollCurrent = Math.min(scrollCurrent, scrollTo);
                    if(easing) {
                        if($el.data("sens") == undefined) $el.data("sens", "back");
                        if(scrollCurrent>scrollFrom) {
                            if($el.data("sens") == "back") {
                                currentTime = 1;
                                $el.data("sens", "go");
                            }
                            else {
                                currentTime++;
                            }
                        }
                        if(scrollCurrent<scrollTo) {
                            if($el.data("sens") == "go") {
                                currentTime = 1;
                                $el.data("sens", "back");
                            }
                            else {
                                currentTime++;
                            }
                        }
                        if(noSmooth) currentTime = totalTime;
                        $el.data("current-time", currentTime);
                    }
                    this._properties.map($.proxy(function(prop) {
                        var defaultProp = 0;
                        var to = data[prop];
                        if (to == undefined) return;
                        if(prop=="scale" || prop=="scaleX" || prop=="scaleY" || prop=="scaleZ" ) {
                            defaultProp = 1;
                        }
                        else {
                            to = to | 0;
                        }
                        var prev = $el.data("_" + prop);
                        if (prev == undefined) prev = defaultProp;
                        var next = ((to-defaultProp) * ((scrollCurrent - scrollFrom) / (scrollTo - scrollFrom))) + defaultProp;
                        var val = prev + (next - prev) / smoothness;
                        if(easing && currentTime>0 && currentTime<=totalTime) {
                            var from = defaultProp;
                            if($el.data("sens") == "back") {
                                from = to;
                                to = -to;
                                easing = easingReturn;
                                totalTime = totalTimeReturn;
                            }
                            val = $.easing[easing](null, currentTime, from, to, totalTime);
                        }
                        val = Math.ceil(val * this.round) / this.round;
                        if(val==prev&&next==to) val = to;
                        if(!properties[prop]) properties[prop] = 0;
                        properties[prop] += val;
                        if (prev != properties[prop]) {
                            $el.data("_" + prop, properties[prop]);
                            applyProperties = true;
                        }
                    }, this));
                }
                if (applyProperties) {
                    if (properties["z"] != undefined) {
                        var perspective = data["perspective"];
                        if (perspective == undefined) perspective = 800;
                        var $parent = $el.parent();
                        if(!$parent.data("style")) $parent.data("style", $parent.attr("style") || "");
                        $parent.attr("style", "perspective:" + perspective + "px; -webkit-perspective:" + perspective + "px; "+ $parent.data("style"));
                    }
                    if(properties["scaleX"] == undefined) properties["scaleX"] = 1;
                    if(properties["scaleY"] == undefined) properties["scaleY"] = 1;
                    if(properties["scaleZ"] == undefined) properties["scaleZ"] = 1;
                    if (properties["scale"] != undefined) {
                        properties["scaleX"] *= properties["scale"];
                        properties["scaleY"] *= properties["scale"];
                        properties["scaleZ"] *= properties["scale"];
                    }
                    var translate3d = "translate3d(" + (properties["x"] ? properties["x"] : 0) + "px, " + (properties["y"] ? properties["y"] : 0) + "px, " + (properties["z"] ? properties["z"] : 0) + "px)";
                    var rotate3d = "rotateX(" + (properties["rotateX"] ? properties["rotateX"] : 0) + "deg) rotateY(" + (properties["rotateY"] ? properties["rotateY"] : 0) + "deg) rotateZ(" + (properties["rotateZ"] ? properties["rotateZ"] : 0) + "deg)";
                    var scale3d = "scaleX(" + properties["scaleX"] + ") scaleY(" + properties["scaleY"] + ") scaleZ(" + properties["scaleZ"] + ")";
                    var cssTransform = translate3d + " " + rotate3d + " " + scale3d + ";";
                    this._log(cssTransform);
                    $el.attr("style", "transform:" + cssTransform + " -webkit-transform:" + cssTransform + " " + style);
                }
            }, this));
            if(window.requestAnimationFrame) {
                window.requestAnimationFrame($.proxy(this._onScroll, this, false));
            }
            else {
                this._requestAnimationFrame($.proxy(this._onScroll, this, false));
            }
        }
    };
})(jQuery);
/*
 * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
 *
 * Uses the built in easing capabilities added In jQuery 1.1
 * to offer multiple easing options
 *
 * TERMS OF USE - jQuery Easing
 * 
 * Open source under the BSD License. 
 * 
 * Copyright © 2008 George McGinley Smith
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
*/

// t: current time, b: begInnIng value, c: change In value, d: duration
jQuery.easing['jswing'] = jQuery.easing['swing'];

jQuery.extend(jQuery.easing,
    {
        def: 'easeOutQuad',
        swing: function (x, t, b, c, d) {
            //alert(jQuery.easing.default);
            return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
        },
        easeInQuad: function (x, t, b, c, d) {
            return c * (t /= d) * t + b;
        },
        easeOutQuad: function (x, t, b, c, d) {
            return -c * (t /= d) * (t - 2) + b;
        },
        easeInOutQuad: function (x, t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t + b;
            return -c / 2 * ((--t) * (t - 2) - 1) + b;
        },
        easeInCubic: function (x, t, b, c, d) {
            return c * (t /= d) * t * t + b;
        },
        easeOutCubic: function (x, t, b, c, d) {
            return c * ((t = t / d - 1) * t * t + 1) + b;
        },
        easeInOutCubic: function (x, t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
            return c / 2 * ((t -= 2) * t * t + 2) + b;
        },
        easeInQuart: function (x, t, b, c, d) {
            return c * (t /= d) * t * t * t + b;
        },
        easeOutQuart: function (x, t, b, c, d) {
            return -c * ((t = t / d - 1) * t * t * t - 1) + b;
        },
        easeInOutQuart: function (x, t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
            return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
        },
        easeInQuint: function (x, t, b, c, d) {
            return c * (t /= d) * t * t * t * t + b;
        },
        easeOutQuint: function (x, t, b, c, d) {
            return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
        },
        easeInOutQuint: function (x, t, b, c, d) {
            if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
            return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
        },
        easeInSine: function (x, t, b, c, d) {
            return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
        },
        easeOutSine: function (x, t, b, c, d) {
            return c * Math.sin(t / d * (Math.PI / 2)) + b;
        },
        easeInOutSine: function (x, t, b, c, d) {
            return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
        },
        easeInExpo: function (x, t, b, c, d) {
            return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
        },
        easeOutExpo: function (x, t, b, c, d) {
            return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
        },
        easeInOutExpo: function (x, t, b, c, d) {
            if (t == 0) return b;
            if (t == d) return b + c;
            if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
            return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
        },
        easeInCirc: function (x, t, b, c, d) {
            return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
        },
        easeOutCirc: function (x, t, b, c, d) {
            return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
        },
        easeInOutCirc: function (x, t, b, c, d) {
            if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
            return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
        },
        easeInElastic: function (x, t, b, c, d) {
            var s = 1.70158; var p = 0; var a = c;
            if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3;
            if (a < Math.abs(c)) { a = c; var s = p / 4; }
            else var s = p / (2 * Math.PI) * Math.asin(c / a);
            return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
        },
        easeOutElastic: function (x, t, b, c, d) {
            var s = 1.70158; var p = 0; var a = c;
            if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3;
            if (a < Math.abs(c)) { a = c; var s = p / 4; }
            else var s = p / (2 * Math.PI) * Math.asin(c / a);
            return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
        },
        easeInOutElastic: function (x, t, b, c, d) {
            var s = 1.70158; var p = 0; var a = c;
            if (t == 0) return b; if ((t /= d / 2) == 2) return b + c; if (!p) p = d * (.3 * 1.5);
            if (a < Math.abs(c)) { a = c; var s = p / 4; }
            else var s = p / (2 * Math.PI) * Math.asin(c / a);
            if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
            return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
        },
        easeInBack: function (x, t, b, c, d, s) {
            if (s == undefined) s = 1.70158;
            return c * (t /= d) * t * ((s + 1) * t - s) + b;
        },
        easeOutBack: function (x, t, b, c, d, s) {
            if (s == undefined) s = 1.70158;
            return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
        },
        easeInOutBack: function (x, t, b, c, d, s) {
            if (s == undefined) s = 1.70158;
            if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
            return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
        },
        easeInBounce: function (x, t, b, c, d) {
            return c - jQuery.easing.easeOutBounce(x, d - t, 0, c, d) + b;
        },
        easeOutBounce: function (x, t, b, c, d) {
            if ((t /= d) < (1 / 2.75)) {
                return c * (7.5625 * t * t) + b;
            } else if (t < (2 / 2.75)) {
                return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
            } else if (t < (2.5 / 2.75)) {
                return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
            } else {
                return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
            }
        },
        easeInOutBounce: function (x, t, b, c, d) {
            if (t < d / 2) return jQuery.easing.easeInBounce(x, t * 2, 0, c, d) * .5 + b;
            return jQuery.easing.easeOutBounce(x, t * 2 - d, 0, c, d) * .5 + c * .5 + b;
        }
    });

/*!
 * GSAP 3.10.4
 * https://greensock.com
 *
 * @license Copyright 2022, GreenSock. All rights reserved.
 * Subject to the terms at https://greensock.com/standard-license or for Club GreenSock members, the agreement issued with that membership.
 * @author: Jack Doyle, jack@greensock.com
 */

!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function o(t){return"string"==typeof t}function p(t){return"function"==typeof t}function q(t){return"number"==typeof t}function r(t){return void 0===t}function s(t){return"object"==typeof t}function t(t){return!1!==t}function u(){return"undefined"!=typeof window}function v(t){return p(t)||o(t)}function M(t){return(h=mt(t,ot))&&he}function N(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function O(t,e){return!e&&console.warn(t)}function P(t,e){return t&&(ot[t]=e)&&h&&(h[t]=e)||ot}function Q(){return 0}function $(t){var e,r,i=t[0];if(s(i)||p(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=pt.length;r--&&!pt[r].targetTest(i););e=pt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Lt(t[r],e)))||t.splice(r,1);return t}function _(t){return t._gsap||$(xt(t))[0]._gsap}function aa(t,e,i){return(i=t[e])&&p(i)?t[e]():r(i)&&t.getAttribute&&t.getAttribute(e)||i}function ba(t,e){return(t=t.split(",")).forEach(e)||t}function ca(t){return Math.round(1e5*t)/1e5||0}function da(t){return Math.round(1e7*t)/1e7||0}function ea(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function fa(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++i<r;);return i<r}function ga(){var t,e,r=ht.length,i=ht.slice(0);for(lt={},t=ht.length=0;t<r;t++)(e=i[t])&&e._lazy&&(e.render(e._lazy[0],e._lazy[1],!0)._lazy=0)}function ha(t,e,r,i){ht.length&&ga(),t.render(e,r,i),ht.length&&ga()}function ia(t){var e=parseFloat(t);return(e||0===e)&&(t+"").match(at).length<2?e:o(t)?t.trim():t}function ja(t){return t}function ka(t,e){for(var r in e)r in t||(t[r]=e[r]);return t}function na(t,e){for(var r in e)"__proto__"!==r&&"constructor"!==r&&"prototype"!==r&&(t[r]=s(e[r])?na(t[r]||(t[r]={}),e[r]):e[r]);return t}function oa(t,e){var r,i={};for(r in t)r in e||(i[r]=t[r]);return i}function pa(e){var r=e.parent||I,i=e.keyframes?function _setKeyframeDefaults(i){return function(t,e){for(var r in e)r in t||"duration"===r&&i||"ease"===r||(t[r]=e[r])}}(J(e.keyframes)):ka;if(t(e.inherit))for(;r;)i(e,r.vars.defaults),r=r.parent||r._dp;return e}function ra(t,e,r,i,n){void 0===r&&(r="_first"),void 0===i&&(i="_last");var a,s=t[i];if(n)for(a=e[n];s&&s[n]>a;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function sa(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function ta(t,e){!t.parent||e&&!t.parent.autoRemoveChildren||t.parent.remove(t),t._act=0}function ua(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function xa(t){return t._repeat?gt(t._tTime,t=t.duration()+t._rDelay)*t:0}function za(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Aa(t){return t._end=da(t._start+(t._tDur/Math.abs(t._ts||t._rts||V)||0))}function Ba(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=da(r._time-(0<t._ts?e/t._ts:((t._dirty?t.totalDuration():t._tDur)-e)/-t._ts)),Aa(t),r._dirty||ua(r,t)),t}function Ca(t,e){var r;if((e._time||e._initted&&!e._dur)&&(r=za(t.rawTime(),e),(!e._dur||bt(0,e.totalDuration(),r)-e._tTime>V)&&e.render(r,!0)),ua(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur<t.duration())for(r=t;r._dp;)0<=r.rawTime()&&r.totalTime(r._tTime),r=r._dp;t._zTime=-V}}function Da(t,e,r,i){return e.parent&&ta(e),e._start=da((q(r)?r:r||t!==I?Tt(t,r,e):t._time)+e._delay),e._end=da(e._start+(e.totalDuration()/Math.abs(e.timeScale())||0)),ra(t,e,"_first","_last",t._sort?"_start":0),vt(e)||(t._recent=e),i||Ca(t,e),t}function Ea(t,e){return(ot.ScrollTrigger||N("scrollTrigger",e))&&ot.ScrollTrigger.create(e,t)}function Fa(t,e,r,i){return Xt(t,e),t._initted?!r&&t._pt&&(t._dur&&!1!==t.vars.lazy||!t._dur&&t.vars.lazy)&&f!==Dt.frame?(ht.push(t),t._lazy=[e,i],1):void 0:1}function Ka(t,e,r,i){var n=t._repeat,a=da(e)||0,s=t._tTime/t._tDur;return s&&!i&&(t._time*=a/t._dur),t._dur=a,t._tDur=n?n<0?1e10:da(a*(n+1)+t._rDelay*n):a,0<s&&!i?Ba(t,t._tTime=t._tDur*s):t.parent&&Aa(t),r||ua(t.parent,t),t}function La(t){return t instanceof Ut?ua(t):Ka(t,t._dur)}function Oa(e,r,i){var n,a,s=q(r[1]),o=(s?2:1)+(e<2?0:1),u=r[o];if(s&&(u.duration=r[1]),u.parent=i,e){for(n=u,a=i;a&&!("immediateRender"in n);)n=a.vars.defaults||{},a=t(a.vars.inherit)&&a.parent;u.immediateRender=t(n.immediateRender),e<2?u.runBackwards=1:u.startAt=r[o-1]}return new $t(r[0],u,r[1+o])}function Pa(t,e){return t||0===t?e(t):e}function Ra(t,e){return o(t)&&(e=st.exec(t))?e[1]:""}function Ua(t,e){return t&&s(t)&&"length"in t&&(!e&&!t.length||t.length-1 in t&&s(t[0]))&&!t.nodeType&&t!==i}function Ya(t){return t.sort(function(){return.5-Math.random()})}function Za(t){if(p(t))return t;var c=s(t)?t:{each:t},m=Bt(c.ease),g=c.from||0,v=parseFloat(c.base)||0,y={},e=0<g&&g<1,T=isNaN(g)||e,b=c.axis,w=g,x=g;return o(g)?w=x={center:.5,edges:.5,end:1}[g]||0:!e&&T&&(w=g[0],x=g[1]),function(t,e,r){var i,n,a,s,o,u,h,l,f,d=(r||c).length,_=y[d];if(!_){if(!(f="auto"===c.grid?0:(c.grid||[1,Y])[1])){for(h=-Y;h<(h=r[f++].getBoundingClientRect().left)&&f<d;);f--}for(_=y[d]=[],i=T?Math.min(f,d)*w-.5:g%f,n=f===Y?0:T?d*x/f-.5:g/f|0,l=Y,u=h=0;u<d;u++)a=u%f-i,s=n-(u/f|0),_[u]=o=b?Math.abs("y"===b?s:a):G(a*a+s*s),h<o&&(h=o),o<l&&(l=o);"random"===g&&Ya(_),_.max=h-l,_.min=l,_.v=d=(parseFloat(c.amount)||parseFloat(c.each)*(d<f?d-1:b?"y"===b?d/f:f:Math.max(f,d/f))||0)*("edges"===g?-1:1),_.b=d<0?v-d:v,_.u=Ra(c.amount||c.each)||0,m=m&&d<0?Ft(m):m}return d=(_[t]-_.min)/_.max||0,da(_.b+(m?m(d):d)*_.v)+_.u}}function $a(r){var i=Math.pow(10,((r+"").split(".")[1]||"").length);return function(t){var e=Math.round(parseFloat(t)/r)*r*i;return(e-e%1)/i+(q(t)?0:Ra(t))}}function _a(u,t){var h,l,e=J(u);return!e&&s(u)&&(h=e=u.radius||Y,u.values?(u=xt(u.values),(l=!q(u[0]))&&(h*=h)):u=$a(u.increment)),Pa(t,e?p(u)?function(t){return l=u(t),Math.abs(l-t)<=h?l:t}:function(t){for(var e,r,i=parseFloat(l?t.x:t),n=parseFloat(l?t.y:0),a=Y,s=0,o=u.length;o--;)(e=l?(e=u[o].x-i)*e+(r=u[o].y-n)*r:Math.abs(u[o]-i))<a&&(a=e,s=o);return s=!h||a<=h?u[s]:t,l||s===t||q(t)?s:s+Ra(t)}:$a(u))}function ab(t,e,r,i){return Pa(J(t)?!e:!0===r?!!(r=0):!i,function(){return J(t)?t[~~(Math.random()*t.length)]:(r=r||1e-5)&&(i=r<1?Math.pow(10,(r+"").length-2):1)&&Math.floor(Math.round((t-r/2+Math.random()*(e-t+.99*r))/r)*r*i)/i})}function eb(e,r,t){return Pa(t,function(t){return e[~~r(t)]})}function hb(t){for(var e,r,i,n,a=0,s="";~(e=t.indexOf("random(",a));)i=t.indexOf(")",e),n="["===t.charAt(e+7),r=t.substr(e+7,i-e-7).match(n?at:tt),s+=t.substr(a,e-a)+ab(n?r:+r[0],n?0:+r[1],+r[2]||1e-5),a=i+1;return s+t.substr(a,t.length-a)}function kb(t,e,r){var i,n,a,s=t.labels,o=Y;for(i in s)(n=s[i]-e)<0==!!r&&n&&o>(n=Math.abs(n))&&(a=i,o=n);return a}function mb(t){return ta(t),t.scrollTrigger&&t.scrollTrigger.kill(!1),t.progress()<1&&Ot(t,"onInterrupt"),t}function rb(t,e,r){return(6*(t+=t<0?1:1<t?-1:0)<1?e+(r-e)*t*6:t<.5?r:3*t<2?e+(r-e)*(2/3-t)*6:e)*Pt+.5|0}function sb(t,e,r){var i,n,a,s,o,u,h,l,f,d,_=t?q(t)?[t>>16,t>>8&Pt,t&Pt]:0:Mt.black;if(!_){if(","===t.substr(-1)&&(t=t.substr(0,t.length-1)),Mt[t])_=Mt[t];else if("#"===t.charAt(0)){if(t.length<6&&(t="#"+(i=t.charAt(1))+i+(n=t.charAt(2))+n+(a=t.charAt(3))+a+(5===t.length?t.charAt(4)+t.charAt(4):"")),9===t.length)return[(_=parseInt(t.substr(1,6),16))>>16,_>>8&Pt,_&Pt,parseInt(t.substr(7),16)/255];_=[(t=parseInt(t.substr(1),16))>>16,t>>8&Pt,t&Pt]}else if("hsl"===t.substr(0,3))if(_=d=t.match(tt),e){if(~t.indexOf("="))return _=t.match(et),r&&_.length<4&&(_[3]=1),_}else s=+_[0]%360/360,o=_[1]/100,i=2*(u=_[2]/100)-(n=u<=.5?u*(o+1):u+o-u*o),3<_.length&&(_[3]*=1),_[0]=rb(s+1/3,i,n),_[1]=rb(s,i,n),_[2]=rb(s-1/3,i,n);else _=t.match(tt)||Mt.transparent;_=_.map(Number)}return e&&!d&&(i=_[0]/Pt,n=_[1]/Pt,a=_[2]/Pt,u=((h=Math.max(i,n,a))+(l=Math.min(i,n,a)))/2,h===l?s=o=0:(f=h-l,o=.5<u?f/(2-h-l):f/(h+l),s=h===i?(n-a)/f+(n<a?6:0):h===n?(a-i)/f+2:(i-n)/f+4,s*=60),_[0]=~~(s+.5),_[1]=~~(100*o+.5),_[2]=~~(100*u+.5)),r&&_.length<4&&(_[3]=1),_}function tb(t){var r=[],i=[],n=-1;return t.split(Ct).forEach(function(t){var e=t.match(rt)||[];r.push.apply(r,e),i.push(n+=e.length+1)}),r.c=i,r}function ub(t,e,r){var i,n,a,s,o="",u=(t+o).match(Ct),h=e?"hsla(":"rgba(",l=0;if(!u)return t;if(u=u.map(function(t){return(t=sb(t,e,1))&&h+(e?t[0]+","+t[1]+"%,"+t[2]+"%,"+t[3]:t.join(","))+")"}),r&&(a=tb(t),(i=r.c).join(o)!==a.c.join(o)))for(s=(n=t.replace(Ct,"1").split(rt)).length-1;l<s;l++)o+=n[l]+(~i.indexOf(l)?u.shift()||h+"0,0,0,0)":(a.length?a:u.length?u:r).shift());if(!n)for(s=(n=t.split(Ct)).length-1;l<s;l++)o+=n[l]+u[l];return o+n[s]}function xb(t){var e,r=t.join(" ");if(Ct.lastIndex=0,Ct.test(r))return e=At.test(r),t[1]=ub(t[1],e),t[0]=ub(t[0],e,tb(t[1])),!0}function Gb(t){var e=(t+"").split("("),r=zt[e[0]];return r&&1<e.length&&r.config?r.config.apply(null,~t.indexOf("{")?[function _parseObjectInString(t){for(var e,r,i,n={},a=t.substr(1,t.length-3).split(":"),s=a[0],o=1,u=a.length;o<u;o++)r=a[o],e=o!==u-1?r.lastIndexOf(","):r.length,i=r.substr(0,e),n[s]=isNaN(i)?i.replace(Et,"").trim():+i,s=r.substr(e+1).trim();return n}(e[1])]:function _valueInParentheses(t){var e=t.indexOf("(")+1,r=t.indexOf(")"),i=t.indexOf("(",e);return t.substring(e,~i&&i<r?t.indexOf(")",r+1):r)}(t).split(",").map(ia)):zt._CE&&Rt.test(t)?zt._CE("",t):r}function Ib(t,e){for(var r,i=t._first;i;)i instanceof Ut?Ib(i,e):!i.vars.yoyoEase||i._yoyo&&i._repeat||i._yoyo===e||(i.timeline?Ib(i.timeline,e):(r=i._ease,i._ease=i._yEase,i._yEase=r,i._yoyo=e)),i=i._next}function Kb(t,e,r,i){void 0===r&&(r=function easeOut(t){return 1-e(1-t)}),void 0===i&&(i=function easeInOut(t){return t<.5?e(2*t)/2:1-e(2*(1-t))/2});var n,a={easeIn:e,easeOut:r,easeInOut:i};return ba(t,function(t){for(var e in zt[t]=ot[t]=a,zt[n=t.toLowerCase()]=r,a)zt[n+("easeIn"===e?".in":"easeOut"===e?".out":".inOut")]=zt[t+"."+e]=a[e]}),a}function Lb(e){return function(t){return t<.5?(1-e(1-2*t))/2:.5+e(2*(t-.5))/2}}function Mb(r,t,e){function em(t){return 1===t?1:i*Math.pow(2,-10*t)*Z((t-a)*n)+1}var i=1<=t?t:1,n=(e||(r?.3:.45))/(t<1?t:1),a=n/X*(Math.asin(1/i)||0),s="out"===r?em:"in"===r?function(t){return 1-em(1-t)}:Lb(em);return n=X/n,s.config=function(t,e){return Mb(r,t,e)},s}function Nb(e,r){function mm(t){return t?--t*t*((r+1)*t+r)+1:0}void 0===r&&(r=1.70158);var t="out"===e?mm:"in"===e?function(t){return 1-mm(1-t)}:Lb(mm);return t.config=function(t){return Nb(e,t)},t}var B,I,i,n,a,h,l,f,d,c,m,g,y,T,b,w,x,k,C,A,D,S,z,R,E,F,U={autoSleep:120,force3D:"auto",nullTargetWarn:1,units:{lineHeight:""}},L={duration:.5,overwrite:!1,delay:0},Y=1e8,V=1/Y,X=2*Math.PI,j=X/4,K=0,G=Math.sqrt,W=Math.cos,Z=Math.sin,H="function"==typeof ArrayBuffer&&ArrayBuffer.isView||function(){},J=Array.isArray,tt=/(?:-?\.?\d|\.)+/gi,et=/[-+=.]*\d+[.e\-+]*\d*[e\-+]*\d*/g,rt=/[-+=.]*\d+[.e-]*\d*[a-z%]*/g,it=/[-+=.]*\d+\.?\d*(?:e-|e\+)?\d*/gi,nt=/[+-]=-?[.\d]+/,at=/[^,'"\[\]\s]+/gi,st=/^[+\-=e\s\d]*\d+[.\d]*([a-z]*|%)\s*$/i,ot={},ut={},ht=[],lt={},ft={},dt={},_t=30,pt=[],ct="",mt=function _merge(t,e){for(var r in e)t[r]=e[r];return t},gt=function _animationCycle(t,e){var r=Math.floor(t/=e);return t&&r===t?r-1:r},vt=function _isFromOrFromStart(t){var e=t.data;return"isFromStart"===e||"isStart"===e},yt={_start:0,endTime:Q,totalDuration:Q},Tt=function _parsePosition(t,e,r){var i,n,a,s=t.labels,u=t._recent||yt,h=t.duration()>=Y?u.endTime(!1):t._dur;return o(e)&&(isNaN(e)||e in s)?(n=e.charAt(0),a="%"===e.substr(-1),i=e.indexOf("="),"<"===n||">"===n?(0<=i&&(e=e.replace(/=/,"")),("<"===n?u._start:u.endTime(0<=u._repeat))+(parseFloat(e.substr(1))||0)*(a?(i<0?u:r).totalDuration()/100:1)):i<0?(e in s||(s[e]=h),s[e]):(n=parseFloat(e.charAt(i-1)+e.substr(i+1)),a&&r&&(n=n/100*(J(r)?r[0]:r).totalDuration()),1<i?_parsePosition(t,e.substr(0,i-1),r)+n:h+n)):null==e?h:+e},bt=function _clamp(t,e,r){return r<t?t:e<r?e:r},wt=[].slice,xt=function toArray(t,e,r){return!o(t)||r||!n&&St()?J(t)?function _flatten(t,e,r){return void 0===r&&(r=[]),t.forEach(function(t){return o(t)&&!e||Ua(t,1)?r.push.apply(r,xt(t)):r.push(t)})||r}(t,r):Ua(t)?wt.call(t,0):t?[t]:[]:wt.call((e||a).querySelectorAll(t),0)},kt=function mapRange(e,t,r,i,n){var a=t-e,s=i-r;return Pa(n,function(t){return r+((t-e)/a*s||0)})},Ot=function _callback(t,e,r){var i,n,a=t.vars,s=a[e];if(s)return i=a[e+"Params"],n=a.callbackScope||t,r&&ht.length&&ga(),i?s.apply(n,i):s.call(n)},Pt=255,Mt={aqua:[0,Pt,Pt],lime:[0,Pt,0],silver:[192,192,192],black:[0,0,0],maroon:[128,0,0],teal:[0,128,128],blue:[0,0,Pt],navy:[0,0,128],white:[Pt,Pt,Pt],olive:[128,128,0],yellow:[Pt,Pt,0],orange:[Pt,165,0],gray:[128,128,128],purple:[128,0,128],green:[0,128,0],red:[Pt,0,0],pink:[Pt,192,203],cyan:[0,Pt,Pt],transparent:[Pt,Pt,Pt,0]},Ct=function(){var t,e="(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#(?:[0-9a-f]{3,4}){1,2}\\b";for(t in Mt)e+="|"+t+"\\b";return new RegExp(e+")","gi")}(),At=/hsl[a]?\(/,Dt=(x=Date.now,k=500,C=33,A=x(),D=A,z=S=1e3/240,T={time:0,frame:0,tick:function tick(){Vk(!0)},deltaRatio:function deltaRatio(t){return b/(1e3/(t||60))},wake:function wake(){l&&(!n&&u()&&(i=n=window,a=i.document||{},ot.gsap=he,(i.gsapVersions||(i.gsapVersions=[])).push(he.version),M(h||i.GreenSockGlobals||!i.gsap&&i||{}),y=i.requestAnimationFrame),m&&T.sleep(),g=y||function(t){return setTimeout(t,z-1e3*T.time+1|0)},c=1,Vk(2))},sleep:function sleep(){(y?i.cancelAnimationFrame:clearTimeout)(m),c=0,g=Q},lagSmoothing:function lagSmoothing(t,e){k=t||1e8,C=Math.min(e,k,0)},fps:function fps(t){S=1e3/(t||240),z=1e3*T.time+S},add:function add(n,t,e){var a=t?function(t,e,r,i){n(t,e,r,i),T.remove(a)}:n;return T.remove(n),R[e?"unshift":"push"](a),St(),a},remove:function remove(t,e){~(e=R.indexOf(t))&&R.splice(e,1)&&e<=w&&w--},_listeners:R=[]}),St=function _wake(){return!c&&Dt.wake()},zt={},Rt=/^[\d.\-M][\d.\-,\s]/,Et=/["']/g,Ft=function _invertEase(e){return function(t){return 1-e(1-t)}},Bt=function _parseEase(t,e){return t&&(p(t)?t:zt[t]||Gb(t))||e};function Vk(t){var e,r,i,n,a=x()-D,s=!0===t;if(k<a&&(A+=a-C),(0<(e=(i=(D+=a)-A)-z)||s)&&(n=++T.frame,b=i-1e3*T.time,T.time=i/=1e3,z+=e+(S<=e?4:S-e),r=1),s||(m=g(Vk)),r)for(w=0;w<R.length;w++)R[w](i,b,n,t)}function Dm(t){return t<F?E*t*t:t<.7272727272727273?E*Math.pow(t-1.5/2.75,2)+.75:t<.9090909090909092?E*(t-=2.25/2.75)*t+.9375:E*Math.pow(t-2.625/2.75,2)+.984375}ba("Linear,Quad,Cubic,Quart,Quint,Strong",function(t,e){var r=e<5?e+1:e;Kb(t+",Power"+(r-1),e?function(t){return Math.pow(t,r)}:function(t){return t},function(t){return 1-Math.pow(1-t,r)},function(t){return t<.5?Math.pow(2*t,r)/2:1-Math.pow(2*(1-t),r)/2})}),zt.Linear.easeNone=zt.none=zt.Linear.easeIn,Kb("Elastic",Mb("in"),Mb("out"),Mb()),E=7.5625,F=1/2.75,Kb("Bounce",function(t){return 1-Dm(1-t)},Dm),Kb("Expo",function(t){return t?Math.pow(2,10*(t-1)):0}),Kb("Circ",function(t){return-(G(1-t*t)-1)}),Kb("Sine",function(t){return 1===t?1:1-W(t*j)}),Kb("Back",Nb("in"),Nb("out"),Nb()),zt.SteppedEase=zt.steps=ot.SteppedEase={config:function config(t,e){void 0===t&&(t=1);var r=1/t,i=t+(e?0:1),n=e?1:0;return function(t){return((i*bt(0,.99999999,t)|0)+n)*r}}},L.ease=zt["quad.out"],ba("onComplete,onUpdate,onStart,onRepeat,onReverseComplete,onInterrupt",function(t){return ct+=t+","+t+"Params,"});var It,Lt=function GSCache(t,e){this.id=K++,(t._gsap=this).target=t,this.harness=e,this.get=e?e.get:aa,this.set=e?e.getSetter:Ht},Nt=((It=Animation.prototype).delay=function delay(t){return t||0===t?(this.parent&&this.parent.smoothChildTiming&&this.startTime(this._start+t-this._delay),this._delay=t,this):this._delay},It.duration=function duration(t){return arguments.length?this.totalDuration(0<this._repeat?t+(t+this._rDelay)*this._repeat:t):this.totalDuration()&&this._dur},It.totalDuration=function totalDuration(t){return arguments.length?(this._dirty=0,Ka(this,this._repeat<0?t:(t-this._repeat*this._rDelay)/(this._repeat+1))):this._tDur},It.totalTime=function totalTime(t,e){if(St(),!arguments.length)return this._tTime;var r=this._dp;if(r&&r.smoothChildTiming&&this._ts){for(Ba(this,t),!r._dp||r.parent||Ca(r,this);r&&r.parent;)r.parent._time!==r._start+(0<=r._ts?r._tTime/r._ts:(r.totalDuration()-r._tTime)/-r._ts)&&r.totalTime(r._tTime,!0),r=r.parent;!this.parent&&this._dp.autoRemoveChildren&&(0<this._ts&&t<this._tDur||this._ts<0&&0<t||!this._tDur&&!t)&&Da(this._dp,this,this._start-this._delay)}return(this._tTime!==t||!this._dur&&!e||this._initted&&Math.abs(this._zTime)===V||!t&&!this._initted&&(this.add||this._ptLookup))&&(this._ts||(this._pTime=t),ha(this,t,e)),this},It.time=function time(t,e){return arguments.length?this.totalTime(Math.min(this.totalDuration(),t+xa(this))%(this._dur+this._rDelay)||(t?this._dur:0),e):this._time},It.totalProgress=function totalProgress(t,e){return arguments.length?this.totalTime(this.totalDuration()*t,e):this.totalDuration()?Math.min(1,this._tTime/this._tDur):this.ratio},It.progress=function progress(t,e){return arguments.length?this.totalTime(this.duration()*(!this._yoyo||1&this.iteration()?t:1-t)+xa(this),e):this.duration()?Math.min(1,this._time/this._dur):this.ratio},It.iteration=function iteration(t,e){var r=this.duration()+this._rDelay;return arguments.length?this.totalTime(this._time+(t-1)*r,e):this._repeat?gt(this._tTime,r)+1:1},It.timeScale=function timeScale(t){if(!arguments.length)return this._rts===-V?0:this._rts;if(this._rts===t)return this;var e=this.parent&&this._ts?za(this.parent._time,this):this._tTime;return this._rts=+t||0,this._ts=this._ps||t===-V?0:this._rts,this.totalTime(bt(-this._delay,this._tDur,e),!0),Aa(this),function _recacheAncestors(t){for(var e=t.parent;e&&e.parent;)e._dirty=1,e.totalDuration(),e=e.parent;return t}(this)},It.paused=function paused(t){return arguments.length?(this._ps!==t&&((this._ps=t)?(this._pTime=this._tTime||Math.max(-this._delay,this.rawTime()),this._ts=this._act=0):(St(),this._ts=this._rts,this.totalTime(this.parent&&!this.parent.smoothChildTiming?this.rawTime():this._tTime||this._pTime,1===this.progress()&&Math.abs(this._zTime)!==V&&(this._tTime-=V)))),this):this._ps},It.startTime=function startTime(t){if(arguments.length){this._start=t;var e=this.parent||this._dp;return!e||!e._sort&&this.parent||Da(e,this,t-this._delay),this}return this._start},It.endTime=function endTime(e){return this._start+(t(e)?this.totalDuration():this.duration())/Math.abs(this._ts||1)},It.rawTime=function rawTime(t){var e=this.parent||this._dp;return e?t&&(!this._ts||this._repeat&&this._time&&this.totalProgress()<1)?this._tTime%(this._dur+this._rDelay):this._ts?za(e.rawTime(t),this):this._tTime:this._tTime},It.globalTime=function globalTime(t){for(var e=this,r=arguments.length?t:e.rawTime();e;)r=e._start+r/(e._ts||1),e=e._dp;return r},It.repeat=function repeat(t){return arguments.length?(this._repeat=t===1/0?-2:t,La(this)):-2===this._repeat?1/0:this._repeat},It.repeatDelay=function repeatDelay(t){if(arguments.length){var e=this._time;return this._rDelay=t,La(this),e?this.time(e):this}return this._rDelay},It.yoyo=function yoyo(t){return arguments.length?(this._yoyo=t,this):this._yoyo},It.seek=function seek(e,r){return this.totalTime(Tt(this,e),t(r))},It.restart=function restart(e,r){return this.play().totalTime(e?-this._delay:0,t(r))},It.play=function play(t,e){return null!=t&&this.seek(t,e),this.reversed(!1).paused(!1)},It.reverse=function reverse(t,e){return null!=t&&this.seek(t||this.totalDuration(),e),this.reversed(!0).paused(!1)},It.pause=function pause(t,e){return null!=t&&this.seek(t,e),this.paused(!0)},It.resume=function resume(){return this.paused(!1)},It.reversed=function reversed(t){return arguments.length?(!!t!==this.reversed()&&this.timeScale(-this._rts||(t?-V:0)),this):this._rts<0},It.invalidate=function invalidate(){return this._initted=this._act=0,this._zTime=-V,this},It.isActive=function isActive(){var t,e=this.parent||this._dp,r=this._start;return!(e&&!(this._ts&&this._initted&&e.isActive()&&(t=e.rawTime(!0))>=r&&t<this.endTime(!0)-V))},It.eventCallback=function eventCallback(t,e,r){var i=this.vars;return 1<arguments.length?(e?(i[t]=e,r&&(i[t+"Params"]=r),"onUpdate"===t&&(this._onUpdate=e)):delete i[t],this):i[t]},It.then=function then(t){var i=this;return new Promise(function(e){function Un(){var t=i.then;i.then=null,p(r)&&(r=r(i))&&(r.then||r===i)&&(i.then=t),e(r),i.then=t}var r=p(t)?t:ja;i._initted&&1===i.totalProgress()&&0<=i._ts||!i._tTime&&i._ts<0?Un():i._prom=Un})},It.kill=function kill(){mb(this)},Animation);function Animation(t){this.vars=t,this._delay=+t.delay||0,(this._repeat=t.repeat===1/0?-2:t.repeat||0)&&(this._rDelay=t.repeatDelay||0,this._yoyo=!!t.yoyo||!!t.yoyoEase),this._ts=1,Ka(this,+t.duration,1,1),this.data=t.data,c||Dt.wake()}ka(Nt.prototype,{_time:0,_start:0,_end:0,_tTime:0,_tDur:0,_dirty:0,_repeat:0,_yoyo:!1,parent:null,_initted:!1,_rDelay:0,_ts:1,_dp:0,ratio:0,_zTime:-V,_prom:0,_ps:!1,_rts:1});var Ut=function(n){function Timeline(e,r){var i;return void 0===e&&(e={}),(i=n.call(this,e)||this).labels={},i.smoothChildTiming=!!e.smoothChildTiming,i.autoRemoveChildren=!!e.autoRemoveChildren,i._sort=t(e.sortChildren),I&&Da(e.parent||I,_assertThisInitialized(i),r),e.reversed&&i.reverse(),e.paused&&i.paused(!0),e.scrollTrigger&&Ea(_assertThisInitialized(i),e.scrollTrigger),i}_inheritsLoose(Timeline,n);var e=Timeline.prototype;return e.to=function to(t,e,r){return Oa(0,arguments,this),this},e.from=function from(t,e,r){return Oa(1,arguments,this),this},e.fromTo=function fromTo(t,e,r,i){return Oa(2,arguments,this),this},e.set=function set(t,e,r){return e.duration=0,e.parent=this,pa(e).repeatDelay||(e.repeat=0),e.immediateRender=!!e.immediateRender,new $t(t,e,Tt(this,r),1),this},e.call=function call(t,e,r){return Da(this,$t.delayedCall(0,t,e),r)},e.staggerTo=function staggerTo(t,e,r,i,n,a,s){return r.duration=e,r.stagger=r.stagger||i,r.onComplete=a,r.onCompleteParams=s,r.parent=this,new $t(t,r,Tt(this,n)),this},e.staggerFrom=function staggerFrom(e,r,i,n,a,s,o){return i.runBackwards=1,pa(i).immediateRender=t(i.immediateRender),this.staggerTo(e,r,i,n,a,s,o)},e.staggerFromTo=function staggerFromTo(e,r,i,n,a,s,o,u){return n.startAt=i,pa(n).immediateRender=t(n.immediateRender),this.staggerTo(e,r,n,a,s,o,u)},e.render=function render(t,e,r){var i,n,a,s,o,u,h,l,f,d,_,p,c=this._time,m=this._dirty?this.totalDuration():this._tDur,g=this._dur,v=t<=0?0:da(t),y=this._zTime<0!=t<0&&(this._initted||!g);if(this!==I&&m<v&&0<=t&&(v=m),v!==this._tTime||r||y){if(c!==this._time&&g&&(v+=this._time-c,t+=this._time-c),i=v,f=this._start,u=!(l=this._ts),y&&(g||(c=this._zTime),!t&&e||(this._zTime=t)),this._repeat){if(_=this._yoyo,o=g+this._rDelay,this._repeat<-1&&t<0)return this.totalTime(100*o+t,e,r);if(i=da(v%o),v===m?(s=this._repeat,i=g):((s=~~(v/o))&&s===v/o&&(i=g,s--),g<i&&(i=g)),d=gt(this._tTime,o),!c&&this._tTime&&d!==s&&(d=s),_&&1&s&&(i=g-i,p=1),s!==d&&!this._lock){var T=_&&1&d,b=T===(_&&1&s);if(s<d&&(T=!T),c=T?0:g,this._lock=1,this.render(c||(p?0:da(s*o)),e,!g)._lock=0,this._tTime=v,!e&&this.parent&&Ot(this,"onRepeat"),this.vars.repeatRefresh&&!p&&(this.invalidate()._lock=1),c&&c!==this._time||u!=!this._ts||this.vars.onRepeat&&!this.parent&&!this._act)return this;if(g=this._dur,m=this._tDur,b&&(this._lock=2,c=T?g:-1e-4,this.render(c,!0),this.vars.repeatRefresh&&!p&&this.invalidate()),this._lock=0,!this._ts&&!u)return this;Ib(this,p)}}if(this._hasPause&&!this._forcing&&this._lock<2&&(h=function _findNextPauseTween(t,e,r){var i;if(e<r)for(i=t._first;i&&i._start<=r;){if("isPause"===i.data&&i._start>e)return i;i=i._next}else for(i=t._last;i&&i._start>=r;){if("isPause"===i.data&&i._start<e)return i;i=i._prev}}(this,da(c),da(i)))&&(v-=i-(i=h._start)),this._tTime=v,this._time=i,this._act=!l,this._initted||(this._onUpdate=this.vars.onUpdate,this._initted=1,this._zTime=t,c=0),!c&&i&&!e&&(Ot(this,"onStart"),this._tTime!==v))return this;if(c<=i&&0<=t)for(n=this._first;n;){if(a=n._next,(n._act||i>=n._start)&&n._ts&&h!==n){if(n.parent!==this)return this.render(t,e,r);if(n.render(0<n._ts?(i-n._start)*n._ts:(n._dirty?n.totalDuration():n._tDur)+(i-n._start)*n._ts,e,r),i!==this._time||!this._ts&&!u){h=0,a&&(v+=this._zTime=-V);break}}n=a}else{n=this._last;for(var w=t<0?t:i;n;){if(a=n._prev,(n._act||w<=n._end)&&n._ts&&h!==n){if(n.parent!==this)return this.render(t,e,r);if(n.render(0<n._ts?(w-n._start)*n._ts:(n._dirty?n.totalDuration():n._tDur)+(w-n._start)*n._ts,e,r),i!==this._time||!this._ts&&!u){h=0,a&&(v+=this._zTime=w?-V:V);break}}n=a}}if(h&&!e&&(this.pause(),h.render(c<=i?0:-V)._zTime=c<=i?1:-1,this._ts))return this._start=f,Aa(this),this.render(t,e,r);this._onUpdate&&!e&&Ot(this,"onUpdate",!0),(v===m&&this._tTime>=this.totalDuration()||!v&&c)&&(f!==this._start&&Math.abs(l)===Math.abs(this._ts)||this._lock||(!t&&g||!(v===m&&0<this._ts||!v&&this._ts<0)||ta(this,1),e||t<0&&!c||!v&&!c&&m||(Ot(this,v===m&&0<=t?"onComplete":"onReverseComplete",!0),!this._prom||v<m&&0<this.timeScale()||this._prom())))}return this},e.add=function add(t,e){var r=this;if(q(e)||(e=Tt(this,e,t)),!(t instanceof Nt)){if(J(t))return t.forEach(function(t){return r.add(t,e)}),this;if(o(t))return this.addLabel(t,e);if(!p(t))return this;t=$t.delayedCall(0,t)}return this!==t?Da(this,t,e):this},e.getChildren=function getChildren(t,e,r,i){void 0===t&&(t=!0),void 0===e&&(e=!0),void 0===r&&(r=!0),void 0===i&&(i=-Y);for(var n=[],a=this._first;a;)a._start>=i&&(a instanceof $t?e&&n.push(a):(r&&n.push(a),t&&n.push.apply(n,a.getChildren(!0,e,r)))),a=a._next;return n},e.getById=function getById(t){for(var e=this.getChildren(1,1,1),r=e.length;r--;)if(e[r].vars.id===t)return e[r]},e.remove=function remove(t){return o(t)?this.removeLabel(t):p(t)?this.killTweensOf(t):(sa(this,t),t===this._recent&&(this._recent=this._last),ua(this))},e.totalTime=function totalTime(t,e){return arguments.length?(this._forcing=1,!this._dp&&this._ts&&(this._start=da(Dt.time-(0<this._ts?t/this._ts:(this.totalDuration()-t)/-this._ts))),n.prototype.totalTime.call(this,t,e),this._forcing=0,this):this._tTime},e.addLabel=function addLabel(t,e){return this.labels[t]=Tt(this,e),this},e.removeLabel=function removeLabel(t){return delete this.labels[t],this},e.addPause=function addPause(t,e,r){var i=$t.delayedCall(0,e||Q,r);return i.data="isPause",this._hasPause=1,Da(this,i,Tt(this,t))},e.removePause=function removePause(t){var e=this._first;for(t=Tt(this,t);e;)e._start===t&&"isPause"===e.data&&ta(e),e=e._next},e.killTweensOf=function killTweensOf(t,e,r){for(var i=this.getTweensOf(t,r),n=i.length;n--;)Yt!==i[n]&&i[n].kill(t,e);return this},e.getTweensOf=function getTweensOf(t,e){for(var r,i=[],n=xt(t),a=this._first,s=q(e);a;)a instanceof $t?fa(a._targets,n)&&(s?(!Yt||a._initted&&a._ts)&&a.globalTime(0)<=e&&a.globalTime(a.totalDuration())>e:!e||a.isActive())&&i.push(a):(r=a.getTweensOf(n,e)).length&&i.push.apply(i,r),a=a._next;return i},e.tweenTo=function tweenTo(t,e){e=e||{};var r,i=this,n=Tt(i,t),a=e.startAt,s=e.onStart,o=e.onStartParams,u=e.immediateRender,h=$t.to(i,ka({ease:e.ease||"none",lazy:!1,immediateRender:!1,time:n,overwrite:"auto",duration:e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale())||V,onStart:function onStart(){if(i.pause(),!r){var t=e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale());h._dur!==t&&Ka(h,t,0,1).render(h._time,!0,!0),r=1}s&&s.apply(h,o||[])}},e));return u?h.render(0):h},e.tweenFromTo=function tweenFromTo(t,e,r){return this.tweenTo(e,ka({startAt:{time:Tt(this,t)}},r))},e.recent=function recent(){return this._recent},e.nextLabel=function nextLabel(t){return void 0===t&&(t=this._time),kb(this,Tt(this,t))},e.previousLabel=function previousLabel(t){return void 0===t&&(t=this._time),kb(this,Tt(this,t),1)},e.currentLabel=function currentLabel(t){return arguments.length?this.seek(t,!0):this.previousLabel(this._time+V)},e.shiftChildren=function shiftChildren(t,e,r){void 0===r&&(r=0);for(var i,n=this._first,a=this.labels;n;)n._start>=r&&(n._start+=t,n._end+=t),n=n._next;if(e)for(i in a)a[i]>=r&&(a[i]+=t);return ua(this)},e.invalidate=function invalidate(){var t=this._first;for(this._lock=0;t;)t.invalidate(),t=t._next;return n.prototype.invalidate.call(this)},e.clear=function clear(t){void 0===t&&(t=!0);for(var e,r=this._first;r;)e=r._next,this.remove(r),r=e;return this._dp&&(this._time=this._tTime=this._pTime=0),t&&(this.labels={}),ua(this)},e.totalDuration=function totalDuration(t){var e,r,i,n=0,a=this,s=a._last,o=Y;if(arguments.length)return a.timeScale((a._repeat<0?a.duration():a.totalDuration())/(a.reversed()?-t:t));if(a._dirty){for(i=a.parent;s;)e=s._prev,s._dirty&&s.totalDuration(),o<(r=s._start)&&a._sort&&s._ts&&!a._lock?(a._lock=1,Da(a,s,r-s._delay,1)._lock=0):o=r,r<0&&s._ts&&(n-=r,(!i&&!a._dp||i&&i.smoothChildTiming)&&(a._start+=r/a._ts,a._time-=r,a._tTime-=r),a.shiftChildren(-r,!1,-Infinity),o=0),s._end>n&&s._ts&&(n=s._end),s=e;Ka(a,a===I&&a._time>n?a._time:n,1,1),a._dirty=0}return a._tDur},Timeline.updateRoot=function updateRoot(t){if(I._ts&&(ha(I,za(t,I)),f=Dt.frame),Dt.frame>=_t){_t+=U.autoSleep||120;var e=I._first;if((!e||!e._ts)&&U.autoSleep&&Dt._listeners.length<2){for(;e&&!e._ts;)e=e._next;e||Dt.sleep()}}},Timeline}(Nt);ka(Ut.prototype,{_lock:0,_hasPause:0,_forcing:0});function Ub(t,e,r,i,n,a){var u,h,l,f;if(ft[t]&&!1!==(u=new ft[t]).init(n,u.rawVars?e[t]:function _processVars(t,e,r,i,n){if(p(t)&&(t=Qt(t,n,e,r,i)),!s(t)||t.style&&t.nodeType||J(t)||H(t))return o(t)?Qt(t,n,e,r,i):t;var a,u={};for(a in t)u[a]=Qt(t[a],n,e,r,i);return u}(e[t],i,n,a,r),r,i,a)&&(r._pt=h=new oe(r._pt,n,t,0,1,u.render,u,0,u.priority),r!==d))for(l=r._ptLookup[r._targets.indexOf(n)],f=u._props.length;f--;)l[u._props[f]]=h;return u}function $b(t,r,e,i){var n,a,s=r.ease||i||"power1.inOut";if(J(r))a=e[t]||(e[t]=[]),r.forEach(function(t,e){return a.push({t:e/(r.length-1)*100,v:t,e:s})});else for(n in r)a=e[n]||(e[n]=[]),"ease"===n||a.push({t:parseFloat(t),v:r[n],e:s})}var Yt,qt,Vt=function _addPropTween(t,e,r,i,n,a,s,u,h){p(i)&&(i=i(n||0,t,a));var l,f=t[e],d="get"!==r?r:p(f)?h?t[e.indexOf("set")||!p(t["get"+e.substr(3)])?e:"get"+e.substr(3)](h):t[e]():f,_=p(f)?h?Zt:Wt:Gt;if(o(i)&&(~i.indexOf("random(")&&(i=hb(i)),"="===i.charAt(1)&&(!(l=ea(d,i)+(Ra(d)||0))&&0!==l||(i=l))),d!==i||qt)return isNaN(d*i)||""===i?(f||e in t||N(e,i),function _addComplexStringPropTween(t,e,r,i,n,a,s){var o,u,h,l,f,d,_,p,c=new oe(this._pt,t,e,0,1,ee,null,n),m=0,g=0;for(c.b=r,c.e=i,r+="",(_=~(i+="").indexOf("random("))&&(i=hb(i)),a&&(a(p=[r,i],t,e),r=p[0],i=p[1]),u=r.match(it)||[];o=it.exec(i);)l=o[0],f=i.substring(m,o.index),h?h=(h+1)%5:"rgba("===f.substr(-5)&&(h=1),l!==u[g++]&&(d=parseFloat(u[g-1])||0,c._pt={_next:c._pt,p:f||1===g?f:",",s:d,c:"="===l.charAt(1)?ea(d,l)-d:parseFloat(l)-d,m:h&&h<4?Math.round:0},m=it.lastIndex);return c.c=m<i.length?i.substring(m,i.length):"",c.fp=s,(nt.test(i)||_)&&(c.e=0),this._pt=c}.call(this,t,e,d,i,_,u||U.stringFilter,h)):(l=new oe(this._pt,t,e,+d||0,i-(d||0),"boolean"==typeof f?te:Jt,0,_),h&&(l.fp=h),s&&l.modifier(s,this,t),this._pt=l)},Xt=function _initTween(e,r){var i,n,a,s,o,u,h,l,f,d,p,c,m,g=e.vars,v=g.ease,y=g.startAt,T=g.immediateRender,b=g.lazy,w=g.onUpdate,x=g.onUpdateParams,k=g.callbackScope,O=g.runBackwards,P=g.yoyoEase,M=g.keyframes,C=g.autoRevert,A=e._dur,D=e._startAt,S=e._targets,z=e.parent,R=z&&"nested"===z.data?z.parent._targets:S,E="auto"===e._overwrite&&!B,F=e.timeline;if(!F||M&&v||(v="none"),e._ease=Bt(v,L.ease),e._yEase=P?Ft(Bt(!0===P?v:P,L.ease)):0,P&&e._yoyo&&!e._repeat&&(P=e._yEase,e._yEase=e._ease,e._ease=P),e._from=!F&&!!g.runBackwards,!F||M&&!g.stagger){if(c=(l=S[0]?_(S[0]).harness:0)&&g[l.prop],i=oa(g,ut),D&&(ta(D.render(-1,!0)),D._lazy=0),y)if(ta(e._startAt=$t.set(S,ka({data:"isStart",overwrite:!1,parent:z,immediateRender:!0,lazy:t(b),startAt:null,delay:0,onUpdate:w,onUpdateParams:x,callbackScope:k,stagger:0},y))),r<0&&!T&&!C&&e._startAt.render(-1,!0),T){if(0<r&&!C&&(e._startAt=0),A&&r<=0)return void(r&&(e._zTime=r))}else!1===C&&(e._startAt=0);else if(O&&A)if(D)C||(e._startAt=0);else if(r&&(T=!1),a=ka({overwrite:!1,data:"isFromStart",lazy:T&&t(b),immediateRender:T,stagger:0,parent:z},i),c&&(a[l.prop]=c),ta(e._startAt=$t.set(S,a)),r<0&&e._startAt.render(-1,!0),e._zTime=r,T){if(!r)return}else _initTween(e._startAt,V);for(e._pt=e._ptCache=0,b=A&&t(b)||b&&!A,n=0;n<S.length;n++){if(h=(o=S[n])._gsap||$(S)[n]._gsap,e._ptLookup[n]=d={},lt[h.id]&&ht.length&&ga(),p=R===S?n:R.indexOf(o),l&&!1!==(f=new l).init(o,c||i,e,p,R)&&(e._pt=s=new oe(e._pt,o,f.name,0,1,f.render,f,0,f.priority),f._props.forEach(function(t){d[t]=s}),f.priority&&(u=1)),!l||c)for(a in i)ft[a]&&(f=Ub(a,i,e,p,o,R))?f.priority&&(u=1):d[a]=s=Vt.call(e,o,a,"get",i[a],p,R,0,g.stringFilter);e._op&&e._op[n]&&e.kill(o,e._op[n]),E&&e._pt&&(Yt=e,I.killTweensOf(o,d,e.globalTime(r)),m=!e.parent,Yt=0),e._pt&&b&&(lt[h.id]=1)}u&&se(e),e._onInit&&e._onInit(e)}e._onUpdate=w,e._initted=(!e._op||e._pt)&&!m,M&&r<=0&&F.render(Y,!0,!0)},Qt=function _parseFuncOrString(t,e,r,i,n){return p(t)?t.call(e,r,i,n):o(t)&&~t.indexOf("random(")?hb(t):t},jt=ct+"repeat,repeatDelay,yoyo,repeatRefresh,yoyoEase,autoRevert",Kt={};ba(jt+",id,stagger,delay,duration,paused,scrollTrigger",function(t){return Kt[t]=1});var $t=function(R){function Tween(e,r,i,n){var a;"number"==typeof r&&(i.duration=r,r=i,i=null);var o,u,h,l,f,d,_,p,c=(a=R.call(this,n?r:pa(r))||this).vars,m=c.duration,g=c.delay,y=c.immediateRender,T=c.stagger,b=c.overwrite,w=c.keyframes,x=c.defaults,k=c.scrollTrigger,P=c.yoyoEase,M=r.parent||I,C=(J(e)||H(e)?q(e[0]):"length"in r)?[e]:xt(e);if(a._targets=C.length?$(C):O("GSAP target "+e+" not found. https://greensock.com",!U.nullTargetWarn)||[],a._ptLookup=[],a._overwrite=b,w||T||v(m)||v(g)){if(r=a.vars,(o=a.timeline=new Ut({data:"nested",defaults:x||{}})).kill(),o.parent=o._dp=_assertThisInitialized(a),o._start=0,T||v(m)||v(g)){if(l=C.length,_=T&&Za(T),s(T))for(f in T)~jt.indexOf(f)&&((p=p||{})[f]=T[f]);for(u=0;u<l;u++)(h=oa(r,Kt)).stagger=0,P&&(h.yoyoEase=P),p&&mt(h,p),d=C[u],h.duration=+Qt(m,_assertThisInitialized(a),u,d,C),h.delay=(+Qt(g,_assertThisInitialized(a),u,d,C)||0)-a._delay,!T&&1===l&&h.delay&&(a._delay=g=h.delay,a._start+=g,h.delay=0),o.to(d,h,_?_(u,d,C):0),o._ease=zt.none;o.duration()?m=g=0:a.timeline=0}else if(w){pa(ka(o.vars.defaults,{ease:"none"})),o._ease=Bt(w.ease||r.ease||"none");var A,D,S,z=0;if(J(w))w.forEach(function(t){return o.to(C,t,">")});else{for(f in h={},w)"ease"===f||"easeEach"===f||$b(f,w[f],h,w.easeEach);for(f in h)for(A=h[f].sort(function(t,e){return t.t-e.t}),u=z=0;u<A.length;u++)(S={ease:(D=A[u]).e,duration:(D.t-(u?A[u-1].t:0))/100*m})[f]=D.v,o.to(C,S,z),z+=S.duration;o.duration()<m&&o.to({},{duration:m-o.duration()})}}m||a.duration(m=o.duration())}else a.timeline=0;return!0!==b||B||(Yt=_assertThisInitialized(a),I.killTweensOf(C),Yt=0),Da(M,_assertThisInitialized(a),i),r.reversed&&a.reverse(),r.paused&&a.paused(!0),(y||!m&&!w&&a._start===da(M._time)&&t(y)&&function _hasNoPausedAncestors(t){return!t||t._ts&&_hasNoPausedAncestors(t.parent)}(_assertThisInitialized(a))&&"nested"!==M.data)&&(a._tTime=-V,a.render(Math.max(0,-g))),k&&Ea(_assertThisInitialized(a),k),a}_inheritsLoose(Tween,R);var e=Tween.prototype;return e.render=function render(t,e,r){var i,n,a,s,o,u,h,l,f,d=this._time,_=this._tDur,p=this._dur,c=_-V<t&&0<=t?_:t<V?0:t;if(p){if(c!==this._tTime||!t||r||!this._initted&&this._tTime||this._startAt&&this._zTime<0!=t<0){if(i=c,l=this.timeline,this._repeat){if(s=p+this._rDelay,this._repeat<-1&&t<0)return this.totalTime(100*s+t,e,r);if(i=da(c%s),c===_?(a=this._repeat,i=p):((a=~~(c/s))&&a===c/s&&(i=p,a--),p<i&&(i=p)),(u=this._yoyo&&1&a)&&(f=this._yEase,i=p-i),o=gt(this._tTime,s),i===d&&!r&&this._initted)return this._tTime=c,this;a!==o&&(l&&this._yEase&&Ib(l,u),!this.vars.repeatRefresh||u||this._lock||(this._lock=r=1,this.render(da(s*a),!0).invalidate()._lock=0))}if(!this._initted){if(Fa(this,t<0?t:i,r,e))return this._tTime=0,this;if(d!==this._time)return this;if(p!==this._dur)return this.render(t,e,r)}if(this._tTime=c,this._time=i,!this._act&&this._ts&&(this._act=1,this._lazy=0),this.ratio=h=(f||this._ease)(i/p),this._from&&(this.ratio=h=1-h),i&&!d&&!e&&(Ot(this,"onStart"),this._tTime!==c))return this;for(n=this._pt;n;)n.r(h,n.d),n=n._next;l&&l.render(t<0?t:!i&&u?-V:l._dur*l._ease(i/this._dur),e,r)||this._startAt&&(this._zTime=t),this._onUpdate&&!e&&(t<0&&this._startAt&&this._startAt.render(t,!0,r),Ot(this,"onUpdate")),this._repeat&&a!==o&&this.vars.onRepeat&&!e&&this.parent&&Ot(this,"onRepeat"),c!==this._tDur&&c||this._tTime!==c||(t<0&&this._startAt&&!this._onUpdate&&this._startAt.render(t,!0,!0),!t&&p||!(c===this._tDur&&0<this._ts||!c&&this._ts<0)||ta(this,1),e||t<0&&!d||!c&&!d||(Ot(this,c===_?"onComplete":"onReverseComplete",!0),!this._prom||c<_&&0<this.timeScale()||this._prom()))}}else!function _renderZeroDurationTween(t,e,r,i){var n,a,s,o=t.ratio,u=e<0||!e&&(!t._start&&function _parentPlayheadIsBeforeStart(t){var e=t.parent;return e&&e._ts&&e._initted&&!e._lock&&(e.rawTime()<0||_parentPlayheadIsBeforeStart(e))}(t)&&(t._initted||!vt(t))||(t._ts<0||t._dp._ts<0)&&!vt(t))?0:1,h=t._rDelay,l=0;if(h&&t._repeat&&(l=bt(0,t._tDur,e),a=gt(l,h),t._yoyo&&1&a&&(u=1-u),a!==gt(t._tTime,h)&&(o=1-u,t.vars.repeatRefresh&&t._initted&&t.invalidate())),u!==o||i||t._zTime===V||!e&&t._zTime){if(!t._initted&&Fa(t,e,i,r))return;for(s=t._zTime,t._zTime=e||(r?V:0),r=r||e&&!s,t.ratio=u,t._from&&(u=1-u),t._time=0,t._tTime=l,n=t._pt;n;)n.r(u,n.d),n=n._next;t._startAt&&e<0&&t._startAt.render(e,!0,!0),t._onUpdate&&!r&&Ot(t,"onUpdate"),l&&t._repeat&&!r&&t.parent&&Ot(t,"onRepeat"),(e>=t._tDur||e<0)&&t.ratio===u&&(u&&ta(t,1),r||(Ot(t,u?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}else t._zTime||(t._zTime=e)}(this,t,e,r);return this},e.targets=function targets(){return this._targets},e.invalidate=function invalidate(){return this._pt=this._op=this._startAt=this._onUpdate=this._lazy=this.ratio=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(),R.prototype.invalidate.call(this)},e.resetTo=function resetTo(t,e,r,i){c||Dt.wake(),this._ts||this.play();var n,a=Math.min(this._dur,(this._dp._time-this._start)*this._ts);return this._initted||Xt(this,a),n=this._ease(a/this._dur),function _updatePropTweens(t,e,r,i,n,a,s){var o,u,h,l=(t._pt&&t._ptCache||(t._ptCache={}))[e];if(!l)for(l=t._ptCache[e]=[],u=t._ptLookup,h=t._targets.length;h--;){if((o=u[h][e])&&o.d&&o.d._pt)for(o=o.d._pt;o&&o.p!==e;)o=o._next;if(!o)return qt=1,t.vars[e]="+=0",Xt(t,s),qt=0,1;l.push(o)}for(h=l.length;h--;)(o=l[h]).s=!i&&0!==i||n?o.s+(i||0)+a*o.c:i,o.c=r-o.s,o.e&&(o.e=ca(r)+Ra(o.e)),o.b&&(o.b=o.s+Ra(o.b))}(this,t,e,r,i,n,a)?this.resetTo(t,e,r,i):(Ba(this,0),this.parent||ra(this._dp,this,"_first","_last",this._dp._sort?"_start":0),this.render(0))},e.kill=function kill(t,e){if(void 0===e&&(e="all"),!(t||e&&"all"!==e))return this._lazy=this._pt=0,this.parent?mb(this):this;if(this.timeline){var r=this.timeline.totalDuration();return this.timeline.killTweensOf(t,e,Yt&&!0!==Yt.vars.overwrite)._first||mb(this),this.parent&&r!==this.timeline.totalDuration()&&Ka(this,this._dur*this.timeline._tDur/r,0,1),this}var i,n,a,s,u,h,l,f=this._targets,d=t?xt(t):f,p=this._ptLookup,c=this._pt;if((!e||"all"===e)&&function _arraysMatch(t,e){for(var r=t.length,i=r===e.length;i&&r--&&t[r]===e[r];);return r<0}(f,d))return"all"===e&&(this._pt=0),mb(this);for(i=this._op=this._op||[],"all"!==e&&(o(e)&&(u={},ba(e,function(t){return u[t]=1}),e=u),e=function _addAliasesToVars(t,e){var r,i,n,a,s=t[0]?_(t[0]).harness:0,o=s&&s.aliases;if(!o)return e;for(i in r=mt({},e),o)if(i in r)for(n=(a=o[i].split(",")).length;n--;)r[a[n]]=r[i];return r}(f,e)),l=f.length;l--;)if(~d.indexOf(f[l]))for(u in n=p[l],"all"===e?(i[l]=e,s=n,a={}):(a=i[l]=i[l]||{},s=e),s)(h=n&&n[u])&&("kill"in h.d&&!0!==h.d.kill(u)||sa(this,h,"_pt"),delete n[u]),"all"!==a&&(a[u]=1);return this._initted&&!this._pt&&c&&mb(this),this},Tween.to=function to(t,e,r){return new Tween(t,e,r)},Tween.from=function from(t,e){return Oa(1,arguments)},Tween.delayedCall=function delayedCall(t,e,r,i){return new Tween(e,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:t,onComplete:e,onReverseComplete:e,onCompleteParams:r,onReverseCompleteParams:r,callbackScope:i})},Tween.fromTo=function fromTo(t,e,r){return Oa(2,arguments)},Tween.set=function set(t,e){return e.duration=0,e.repeatDelay||(e.repeat=0),new Tween(t,e)},Tween.killTweensOf=function killTweensOf(t,e,r){return I.killTweensOf(t,e,r)},Tween}(Nt);ka($t.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0}),ba("staggerTo,staggerFrom,staggerFromTo",function(r){$t[r]=function(){var t=new Ut,e=wt.call(arguments,0);return e.splice("staggerFromTo"===r?5:4,0,0),t[r].apply(t,e)}});function gc(t,e,r){return t.setAttribute(e,r)}function oc(t,e,r,i){i.mSet(t,e,i.m.call(i.tween,r,i.mt),i)}var Gt=function _setterPlain(t,e,r){return t[e]=r},Wt=function _setterFunc(t,e,r){return t[e](r)},Zt=function _setterFuncWithParam(t,e,r,i){return t[e](i.fp,r)},Ht=function _getSetter(t,e){return p(t[e])?Wt:r(t[e])&&t.setAttribute?gc:Gt},Jt=function _renderPlain(t,e){return e.set(e.t,e.p,Math.round(1e6*(e.s+e.c*t))/1e6,e)},te=function _renderBoolean(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},ee=function _renderComplexString(t,e){var r=e._pt,i="";if(!t&&e.b)i=e.b;else if(1===t&&e.e)i=e.e;else{for(;r;)i=r.p+(r.m?r.m(r.s+r.c*t):Math.round(1e4*(r.s+r.c*t))/1e4)+i,r=r._next;i+=e.c}e.set(e.t,e.p,i,e)},re=function _renderPropTweens(t,e){for(var r=e._pt;r;)r.r(t,r.d),r=r._next},ie=function _addPluginModifier(t,e,r,i){for(var n,a=this._pt;a;)n=a._next,a.p===i&&a.modifier(t,e,r),a=n},ne=function _killPropTweensOf(t){for(var e,r,i=this._pt;i;)r=i._next,i.p===t&&!i.op||i.op===t?sa(this,i,"_pt"):i.dep||(e=1),i=r;return!e},se=function _sortPropTweensByPriority(t){for(var e,r,i,n,a=t._pt;a;){for(e=a._next,r=i;r&&r.pr>a.pr;)r=r._next;(a._prev=r?r._prev:n)?a._prev._next=a:i=a,(a._next=r)?r._prev=a:n=a,a=e}t._pt=i},oe=(PropTween.prototype.modifier=function modifier(t,e,r){this.mSet=this.mSet||this.set,this.set=oc,this.m=t,this.mt=r,this.tween=e},PropTween);function PropTween(t,e,r,i,n,a,s,o,u){this.t=e,this.s=i,this.c=n,this.p=r,this.r=a||Jt,this.d=s||this,this.set=o||Gt,this.pr=u||0,(this._next=t)&&(t._prev=this)}ba(ct+"parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert,scrollTrigger",function(t){return ut[t]=1}),ot.TweenMax=ot.TweenLite=$t,ot.TimelineLite=ot.TimelineMax=Ut,I=new Ut({sortChildren:!1,defaults:L,autoRemoveChildren:!0,id:"root",smoothChildTiming:!0}),U.stringFilter=xb;var ue={registerPlugin:function registerPlugin(){for(var t=arguments.length,e=new Array(t),r=0;r<t;r++)e[r]=arguments[r];e.forEach(function(t){return function _createPlugin(t){var e=(t=!t.name&&t.default||t).name,r=p(t),i=e&&!r&&t.init?function(){this._props=[]}:t,n={init:Q,render:re,add:Vt,kill:ne,modifier:ie,rawVars:0},a={targetTest:0,get:0,getSetter:Ht,aliases:{},register:0};if(St(),t!==i){if(ft[e])return;ka(i,ka(oa(t,n),a)),mt(i.prototype,mt(n,oa(t,a))),ft[i.prop=e]=i,t.targetTest&&(pt.push(i),ut[e]=1),e=("css"===e?"CSS":e.charAt(0).toUpperCase()+e.substr(1))+"Plugin"}P(e,i),t.register&&t.register(he,i,oe)}(t)})},timeline:function timeline(t){return new Ut(t)},getTweensOf:function getTweensOf(t,e){return I.getTweensOf(t,e)},getProperty:function getProperty(i,t,e,r){o(i)&&(i=xt(i)[0]);var n=_(i||{}).get,a=e?ja:ia;return"native"===e&&(e=""),i?t?a((ft[t]&&ft[t].get||n)(i,t,e,r)):function(t,e,r){return a((ft[t]&&ft[t].get||n)(i,t,e,r))}:i},quickSetter:function quickSetter(r,e,i){if(1<(r=xt(r)).length){var n=r.map(function(t){return he.quickSetter(t,e,i)}),a=n.length;return function(t){for(var e=a;e--;)n[e](t)}}r=r[0]||{};var s=ft[e],o=_(r),u=o.harness&&(o.harness.aliases||{})[e]||e,h=s?function(t){var e=new s;d._pt=0,e.init(r,i?t+i:t,d,0,[r]),e.render(1,e),d._pt&&re(1,d)}:o.set(r,u);return s?h:function(t){return h(r,u,i?t+i:t,o,1)}},quickTo:function quickTo(t,i,e){function iw(t,e,r){return n.resetTo(i,t,e,r)}var r,n=he.to(t,mt(((r={})[i]="+=0.1",r.paused=!0,r),e||{}));return iw.tween=n,iw},isTweening:function isTweening(t){return 0<I.getTweensOf(t,!0).length},defaults:function defaults(t){return t&&t.ease&&(t.ease=Bt(t.ease,L.ease)),na(L,t||{})},config:function config(t){return na(U,t||{})},registerEffect:function registerEffect(t){var i=t.name,n=t.effect,e=t.plugins,a=t.defaults,r=t.extendTimeline;(e||"").split(",").forEach(function(t){return t&&!ft[t]&&!ot[t]&&O(i+" effect requires "+t+" plugin.")}),dt[i]=function(t,e,r){return n(xt(t),ka(e||{},a),r)},r&&(Ut.prototype[i]=function(t,e,r){return this.add(dt[i](t,s(e)?e:(r=e)&&{},this),r)})},registerEase:function registerEase(t,e){zt[t]=Bt(e)},parseEase:function parseEase(t,e){return arguments.length?Bt(t,e):zt},getById:function getById(t){return I.getById(t)},exportRoot:function exportRoot(e,r){void 0===e&&(e={});var i,n,a=new Ut(e);for(a.smoothChildTiming=t(e.smoothChildTiming),I.remove(a),a._dp=0,a._time=a._tTime=I._time,i=I._first;i;)n=i._next,!r&&!i._dur&&i instanceof $t&&i.vars.onComplete===i._targets[0]||Da(a,i,i._start-i._delay),i=n;return Da(I,a,0),a},utils:{wrap:function wrap(e,t,r){var i=t-e;return J(e)?eb(e,wrap(0,e.length),t):Pa(r,function(t){return(i+(t-e)%i)%i+e})},wrapYoyo:function wrapYoyo(e,t,r){var i=t-e,n=2*i;return J(e)?eb(e,wrapYoyo(0,e.length-1),t):Pa(r,function(t){return e+(i<(t=(n+(t-e)%n)%n||0)?n-t:t)})},distribute:Za,random:ab,snap:_a,normalize:function normalize(t,e,r){return kt(t,e,0,1,r)},getUnit:Ra,clamp:function clamp(e,r,t){return Pa(t,function(t){return bt(e,r,t)})},splitColor:sb,toArray:xt,selector:function selector(r){return r=xt(r)[0]||O("Invalid scope")||{},function(t){var e=r.current||r.nativeElement||r;return xt(t,e.querySelectorAll?e:e===r?O("Invalid scope")||a.createElement("div"):r)}},mapRange:kt,pipe:function pipe(){for(var t=arguments.length,e=new Array(t),r=0;r<t;r++)e[r]=arguments[r];return function(t){return e.reduce(function(t,e){return e(t)},t)}},unitize:function unitize(e,r){return function(t){return e(parseFloat(t))+(r||Ra(t))}},interpolate:function interpolate(e,r,t,i){var n=isNaN(e+r)?0:function(t){return(1-t)*e+t*r};if(!n){var a,s,u,h,l,f=o(e),d={};if(!0===t&&(i=1)&&(t=null),f)e={p:e},r={p:r};else if(J(e)&&!J(r)){for(u=[],h=e.length,l=h-2,s=1;s<h;s++)u.push(interpolate(e[s-1],e[s]));h--,n=function func(t){t*=h;var e=Math.min(l,~~t);return u[e](t-e)},t=r}else i||(e=mt(J(e)?[]:{},e));if(!u){for(a in r)Vt.call(d,e,a,"get",r[a]);n=function func(t){return re(t,d)||(f?e.p:e)}}}return Pa(t,n)},shuffle:Ya},install:M,effects:dt,ticker:Dt,updateRoot:Ut.updateRoot,plugins:ft,globalTimeline:I,core:{PropTween:oe,globals:P,Tween:$t,Timeline:Ut,Animation:Nt,getCache:_,_removeLinkedListItem:sa,suppressOverwrites:function suppressOverwrites(t){return B=t}}};ba("to,from,fromTo,delayedCall,set,killTweensOf",function(t){return ue[t]=$t[t]}),Dt.add(Ut.updateRoot),d=ue.to({},{duration:0});function sc(t,e){for(var r=t._pt;r&&r.p!==e&&r.op!==e&&r.fp!==e;)r=r._next;return r}function uc(t,n){return{name:t,rawVars:1,init:function init(t,i,e){e._onInit=function(t){var e,r;if(o(i)&&(e={},ba(i,function(t){return e[t]=1}),i=e),n){for(r in e={},i)e[r]=n(i[r]);i=e}!function _addModifiers(t,e){var r,i,n,a=t._targets;for(r in e)for(i=a.length;i--;)(n=(n=t._ptLookup[i][r])&&n.d)&&(n._pt&&(n=sc(n,r)),n&&n.modifier&&n.modifier(e[r],t,a[i],r))}(t,i)}}}}var he=ue.registerPlugin({name:"attr",init:function init(t,e,r,i,n){var a,s;for(a in e)(s=this.add(t,"setAttribute",(t.getAttribute(a)||0)+"",e[a],i,n,0,0,a))&&(s.op=a),this._props.push(a)}},{name:"endArray",init:function init(t,e){for(var r=e.length;r--;)this.add(t,r,t[r]||0,e[r])}},uc("roundProps",$a),uc("modifiers"),uc("snap",_a))||ue;$t.version=Ut.version=he.version="3.10.4",l=1,u()&&St();function dd(t,e){return e.set(e.t,e.p,Math.round(1e4*(e.s+e.c*t))/1e4+e.u,e)}function ed(t,e){return e.set(e.t,e.p,1===t?e.e:Math.round(1e4*(e.s+e.c*t))/1e4+e.u,e)}function fd(t,e){return e.set(e.t,e.p,t?Math.round(1e4*(e.s+e.c*t))/1e4+e.u:e.b,e)}function gd(t,e){var r=e.s+e.c*t;e.set(e.t,e.p,~~(r+(r<0?-.5:.5))+e.u,e)}function hd(t,e){return e.set(e.t,e.p,t?e.e:e.b,e)}function id(t,e){return e.set(e.t,e.p,1!==t?e.b:e.e,e)}function jd(t,e,r){return t.style[e]=r}function kd(t,e,r){return t.style.setProperty(e,r)}function ld(t,e,r){return t._gsap[e]=r}function md(t,e,r){return t._gsap.scaleX=t._gsap.scaleY=r}function nd(t,e,r,i,n){var a=t._gsap;a.scaleX=a.scaleY=r,a.renderTransform(n,a)}function od(t,e,r,i,n){var a=t._gsap;a[e]=r,a.renderTransform(n,a)}function sd(t,e){var r=fe.createElementNS?fe.createElementNS((e||"http://www.w3.org/1999/xhtml").replace(/^https/,"http"),t):fe.createElement(t);return r.style?r:fe.createElement(t)}function td(t,e,r){var i=getComputedStyle(t);return i[e]||i.getPropertyValue(e.replace(Ne,"-$1").toLowerCase())||i.getPropertyValue(e)||!r&&td(t,je(e)||e,1)||""}function wd(){(function _windowExists(){return"undefined"!=typeof window})()&&window.document&&(le=window,fe=le.document,de=fe.documentElement,pe=sd("div")||{style:{}},sd("div"),Ve=je(Ve),Xe=Ve+"Origin",pe.style.cssText="border-width:0;line-height:0;position:absolute;padding:0",me=!!je("perspective"),_e=1)}function xd(t){var e,r=sd("svg",this.ownerSVGElement&&this.ownerSVGElement.getAttribute("xmlns")||"http://www.w3.org/2000/svg"),i=this.parentNode,n=this.nextSibling,a=this.style.cssText;if(de.appendChild(r),r.appendChild(this),this.style.display="block",t)try{e=this.getBBox(),this._gsapBBox=this.getBBox,this.getBBox=xd}catch(t){}else this._gsapBBox&&(e=this._gsapBBox());return i&&(n?i.insertBefore(this,n):i.appendChild(this)),de.removeChild(r),this.style.cssText=a,e}function yd(t,e){for(var r=e.length;r--;)if(t.hasAttribute(e[r]))return t.getAttribute(e[r])}function zd(e){var r;try{r=e.getBBox()}catch(t){r=xd.call(e,!0)}return r&&(r.width||r.height)||e.getBBox===xd||(r=xd.call(e,!0)),!r||r.width||r.x||r.y?r:{x:+yd(e,["x","cx","x1"])||0,y:+yd(e,["y","cy","y1"])||0,width:0,height:0}}function Ad(t){return!(!t.getCTM||t.parentNode&&!t.ownerSVGElement||!zd(t))}function Bd(t,e){if(e){var r=t.style;e in Fe&&e!==Xe&&(e=Ve),r.removeProperty?("ms"!==e.substr(0,2)&&"webkit"!==e.substr(0,6)||(e="-"+e),r.removeProperty(e.replace(Ne,"-$1").toLowerCase())):r.removeAttribute(e)}}function Cd(t,e,r,i,n,a){var s=new oe(t._pt,e,r,0,1,a?id:hd);return(t._pt=s).b=i,s.e=n,t._props.push(r),s}function Ed(t,e,r,i){var n,a,s,o,u=parseFloat(r)||0,h=(r+"").trim().substr((u+"").length)||"px",l=pe.style,f=Ue.test(e),d="svg"===t.tagName.toLowerCase(),p=(d?"client":"offset")+(f?"Width":"Height"),c="px"===i,m="%"===i;return i===h||!u||Ke[i]||Ke[h]?u:("px"===h||c||(u=Ed(t,e,r,"px")),o=t.getCTM&&Ad(t),!m&&"%"!==h||!Fe[e]&&!~e.indexOf("adius")?(l[f?"width":"height"]=100+(c?h:i),a=~e.indexOf("adius")||"em"===i&&t.appendChild&&!d?t:t.parentNode,o&&(a=(t.ownerSVGElement||{}).parentNode),a&&a!==fe&&a.appendChild||(a=fe.body),(s=a._gsap)&&m&&s.width&&f&&s.time===Dt.time?ca(u/s.width*100):(!m&&"%"!==h||(l.position=td(t,"position")),a===t&&(l.position="static"),a.appendChild(pe),n=pe[p],a.removeChild(pe),l.position="absolute",f&&m&&((s=_(a)).time=Dt.time,s.width=a[p]),ca(c?n*u/100:n&&u?100/n*u:0))):(n=o?t.getBBox()[f?"width":"height"]:t[p],ca(m?u/n*100:u/100*n)))}function Fd(t,e,r,i){var n;return _e||wd(),e in qe&&"transform"!==e&&~(e=qe[e]).indexOf(",")&&(e=e.split(",")[0]),Fe[e]&&"transform"!==e?(n=He(t,i),n="transformOrigin"!==e?n[e]:n.svg?n.origin:Je(td(t,Xe))+" "+n.zOrigin+"px"):(n=t.style[e])&&"auto"!==n&&!i&&!~(n+"").indexOf("calc(")||(n=Ge[e]&&Ge[e](t,e,r)||td(t,e)||aa(t,e)||("opacity"===e?1:0)),r&&!~(n+"").trim().indexOf(" ")?Ed(t,e,n,r)+r:n}function Gd(t,e,r,i){if(!r||"none"===r){var n=je(e,t,1),a=n&&td(t,n,1);a&&a!==r?(e=n,r=a):"borderColor"===e&&(r=td(t,"borderTopColor"))}var s,o,u,h,l,f,d,_,p,c,m,g=new oe(this._pt,t.style,e,0,1,ee),v=0,y=0;if(g.b=r,g.e=i,r+="","auto"===(i+="")&&(t.style[e]=i,i=td(t,e)||i,t.style[e]=r),xb(s=[r,i]),i=s[1],u=(r=s[0]).match(rt)||[],(i.match(rt)||[]).length){for(;o=rt.exec(i);)d=o[0],p=i.substring(v,o.index),l?l=(l+1)%5:"rgba("!==p.substr(-5)&&"hsla("!==p.substr(-5)||(l=1),d!==(f=u[y++]||"")&&(h=parseFloat(f)||0,m=f.substr((h+"").length),"="===d.charAt(1)&&(d=ea(h,d)+m),_=parseFloat(d),c=d.substr((_+"").length),v=rt.lastIndex-c.length,c||(c=c||U.units[e]||m,v===i.length&&(i+=c,g.e+=c)),m!==c&&(h=Ed(t,e,f,c)||0),g._pt={_next:g._pt,p:p||1===y?p:",",s:h,c:_-h,m:l&&l<4||"zIndex"===e?Math.round:0});g.c=v<i.length?i.substring(v,i.length):""}else g.r="display"===e&&"none"===i?id:hd;return nt.test(i)&&(g.e=0),this._pt=g}function Id(t){var e=t.split(" "),r=e[0],i=e[1]||"50%";return"top"!==r&&"bottom"!==r&&"left"!==i&&"right"!==i||(t=r,r=i,i=t),e[0]=$e[r]||r,e[1]=$e[i]||i,e.join(" ")}function Jd(t,e){if(e.tween&&e.tween._time===e.tween._dur){var r,i,n,a=e.t,s=a.style,o=e.u,u=a._gsap;if("all"===o||!0===o)s.cssText="",i=1;else for(n=(o=o.split(",")).length;-1<--n;)r=o[n],Fe[r]&&(i=1,r="transformOrigin"===r?Xe:Ve),Bd(a,r);i&&(Bd(a,Ve),u&&(u.svg&&a.removeAttribute("transform"),He(a,1),u.uncache=1))}}function Nd(t){return"matrix(1, 0, 0, 1, 0, 0)"===t||"none"===t||!t}function Od(t){var e=td(t,Ve);return Nd(e)?We:e.substr(7).match(et).map(ca)}function Pd(t,e){var r,i,n,a,s=t._gsap||_(t),o=t.style,u=Od(t);return s.svg&&t.getAttribute("transform")?"1,0,0,1,0,0"===(u=[(n=t.transform.baseVal.consolidate().matrix).a,n.b,n.c,n.d,n.e,n.f]).join(",")?We:u:(u!==We||t.offsetParent||t===de||s.svg||(n=o.display,o.display="block",(r=t.parentNode)&&t.offsetParent||(a=1,i=t.nextSibling,de.appendChild(t)),u=Od(t),n?o.display=n:Bd(t,"display"),a&&(i?r.insertBefore(t,i):r?r.appendChild(t):de.removeChild(t))),e&&6<u.length?[u[0],u[1],u[4],u[5],u[12],u[13]]:u)}function Qd(t,e,r,i,n,a){var s,o,u,h=t._gsap,l=n||Pd(t,!0),f=h.xOrigin||0,d=h.yOrigin||0,_=h.xOffset||0,p=h.yOffset||0,c=l[0],m=l[1],g=l[2],v=l[3],y=l[4],T=l[5],b=e.split(" "),w=parseFloat(b[0])||0,x=parseFloat(b[1])||0;r?l!==We&&(o=c*v-m*g)&&(u=w*(-m/o)+x*(c/o)-(c*T-m*y)/o,w=w*(v/o)+x*(-g/o)+(g*T-v*y)/o,x=u):(w=(s=zd(t)).x+(~b[0].indexOf("%")?w/100*s.width:w),x=s.y+(~(b[1]||b[0]).indexOf("%")?x/100*s.height:x)),i||!1!==i&&h.smooth?(y=w-f,T=x-d,h.xOffset=_+(y*c+T*g)-y,h.yOffset=p+(y*m+T*v)-T):h.xOffset=h.yOffset=0,h.xOrigin=w,h.yOrigin=x,h.smooth=!!i,h.origin=e,h.originIsAbsolute=!!r,t.style[Xe]="0px 0px",a&&(Cd(a,h,"xOrigin",f,w),Cd(a,h,"yOrigin",d,x),Cd(a,h,"xOffset",_,h.xOffset),Cd(a,h,"yOffset",p,h.yOffset)),t.setAttribute("data-svg-origin",w+" "+x)}function Td(t,e,r){var i=Ra(e);return ca(parseFloat(e)+parseFloat(Ed(t,"x",r+"px",i)))+i}function $d(t,e,r,i,n){var a,s,u=360,h=o(n),l=parseFloat(n)*(h&&~n.indexOf("rad")?Be:1)-i,f=i+l+"deg";return h&&("short"===(a=n.split("_")[1])&&(l%=u)!==l%180&&(l+=l<0?u:-u),"cw"===a&&l<0?l=(l+36e9)%u-~~(l/u)*u:"ccw"===a&&0<l&&(l=(l-36e9)%u-~~(l/u)*u)),t._pt=s=new oe(t._pt,e,r,i,l,ed),s.e=f,s.u="deg",t._props.push(r),s}function _d(t,e){for(var r in e)t[r]=e[r];return t}function ae(t,e,r){var i,n,a,s,o,u,h,l=_d({},r._gsap),f=r.style;for(n in l.svg?(a=r.getAttribute("transform"),r.setAttribute("transform",""),f[Ve]=e,i=He(r,1),Bd(r,Ve),r.setAttribute("transform",a)):(a=getComputedStyle(r)[Ve],f[Ve]=e,i=He(r,1),f[Ve]=a),Fe)(a=l[n])!==(s=i[n])&&"perspective,force3D,transformOrigin,svgOrigin".indexOf(n)<0&&(o=Ra(a)!==(h=Ra(s))?Ed(r,n,a,h):parseFloat(a),u=parseFloat(s),t._pt=new oe(t._pt,i,n,o,u-o,dd),t._pt.u=h||0,t._props.push(n));_d(i,l)}var le,fe,de,_e,pe,ce,me,ge=zt.Power0,ve=zt.Power1,ye=zt.Power2,Te=zt.Power3,be=zt.Power4,we=zt.Linear,xe=zt.Quad,ke=zt.Cubic,Oe=zt.Quart,Pe=zt.Quint,Me=zt.Strong,Ce=zt.Elastic,Ae=zt.Back,De=zt.SteppedEase,Se=zt.Bounce,ze=zt.Sine,Re=zt.Expo,Ee=zt.Circ,Fe={},Be=180/Math.PI,Ie=Math.PI/180,Le=Math.atan2,Ne=/([A-Z])/g,Ue=/(left|right|width|margin|padding|x)/i,Ye=/[\s,\(]\S/,qe={autoAlpha:"opacity,visibility",scale:"scaleX,scaleY",alpha:"opacity"},Ve="transform",Xe=Ve+"Origin",Qe="O,Moz,ms,Ms,Webkit".split(","),je=function _checkPropPrefix(t,e,r){var i=(e||pe).style,n=5;if(t in i&&!r)return t;for(t=t.charAt(0).toUpperCase()+t.substr(1);n--&&!(Qe[n]+t in i););return n<0?null:(3===n?"ms":0<=n?Qe[n]:"")+t},Ke={deg:1,rad:1,turn:1},$e={top:"0%",bottom:"100%",left:"0%",right:"100%",center:"50%"},Ge={clearProps:function clearProps(t,e,r,i,n){if("isFromStart"!==n.data){var a=t._pt=new oe(t._pt,e,r,0,0,Jd);return a.u=i,a.pr=-10,a.tween=n,t._props.push(r),1}}},We=[1,0,0,1,0,0],Ze={},He=function _parseTransform(t,e){var r=t._gsap||new Lt(t);if("x"in r&&!e&&!r.uncache)return r;var i,n,a,s,o,u,h,l,f,d,_,p,c,m,g,v,y,T,b,w,x,k,O,P,M,C,A,D,S,z,R,E,F=t.style,B=r.scaleX<0,I="deg",L=td(t,Xe)||"0";return i=n=a=u=h=l=f=d=_=0,s=o=1,r.svg=!(!t.getCTM||!Ad(t)),m=Pd(t,r.svg),r.svg&&(P=(!r.uncache||"0px 0px"===L)&&!e&&t.getAttribute("data-svg-origin"),Qd(t,P||L,!!P||r.originIsAbsolute,!1!==r.smooth,m)),p=r.xOrigin||0,c=r.yOrigin||0,m!==We&&(T=m[0],b=m[1],w=m[2],x=m[3],i=k=m[4],n=O=m[5],6===m.length?(s=Math.sqrt(T*T+b*b),o=Math.sqrt(x*x+w*w),u=T||b?Le(b,T)*Be:0,(f=w||x?Le(w,x)*Be+u:0)&&(o*=Math.abs(Math.cos(f*Ie))),r.svg&&(i-=p-(p*T+c*w),n-=c-(p*b+c*x))):(E=m[6],z=m[7],A=m[8],D=m[9],S=m[10],R=m[11],i=m[12],n=m[13],a=m[14],h=(g=Le(E,S))*Be,g&&(P=k*(v=Math.cos(-g))+A*(y=Math.sin(-g)),M=O*v+D*y,C=E*v+S*y,A=k*-y+A*v,D=O*-y+D*v,S=E*-y+S*v,R=z*-y+R*v,k=P,O=M,E=C),l=(g=Le(-w,S))*Be,g&&(v=Math.cos(-g),R=x*(y=Math.sin(-g))+R*v,T=P=T*v-A*y,b=M=b*v-D*y,w=C=w*v-S*y),u=(g=Le(b,T))*Be,g&&(P=T*(v=Math.cos(g))+b*(y=Math.sin(g)),M=k*v+O*y,b=b*v-T*y,O=O*v-k*y,T=P,k=M),h&&359.9<Math.abs(h)+Math.abs(u)&&(h=u=0,l=180-l),s=ca(Math.sqrt(T*T+b*b+w*w)),o=ca(Math.sqrt(O*O+E*E)),g=Le(k,O),f=2e-4<Math.abs(g)?g*Be:0,_=R?1/(R<0?-R:R):0),r.svg&&(P=t.getAttribute("transform"),r.forceCSS=t.setAttribute("transform","")||!Nd(td(t,Ve)),P&&t.setAttribute("transform",P))),90<Math.abs(f)&&Math.abs(f)<270&&(B?(s*=-1,f+=u<=0?180:-180,u+=u<=0?180:-180):(o*=-1,f+=f<=0?180:-180)),e=e||r.uncache,r.x=i-((r.xPercent=i&&(!e&&r.xPercent||(Math.round(t.offsetWidth/2)===Math.round(-i)?-50:0)))?t.offsetWidth*r.xPercent/100:0)+"px",r.y=n-((r.yPercent=n&&(!e&&r.yPercent||(Math.round(t.offsetHeight/2)===Math.round(-n)?-50:0)))?t.offsetHeight*r.yPercent/100:0)+"px",r.z=a+"px",r.scaleX=ca(s),r.scaleY=ca(o),r.rotation=ca(u)+I,r.rotationX=ca(h)+I,r.rotationY=ca(l)+I,r.skewX=f+I,r.skewY=d+I,r.transformPerspective=_+"px",(r.zOrigin=parseFloat(L.split(" ")[2])||0)&&(F[Xe]=Je(L)),r.xOffset=r.yOffset=0,r.force3D=U.force3D,r.renderTransform=r.svg?ar:me?nr:tr,r.uncache=0,r},Je=function _firstTwoOnly(t){return(t=t.split(" "))[0]+" "+t[1]},tr=function _renderNon3DTransforms(t,e){e.z="0px",e.rotationY=e.rotationX="0deg",e.force3D=0,nr(t,e)},er="0deg",rr="0px",ir=") ",nr=function _renderCSSTransforms(t,e){var r=e||this,i=r.xPercent,n=r.yPercent,a=r.x,s=r.y,o=r.z,u=r.rotation,h=r.rotationY,l=r.rotationX,f=r.skewX,d=r.skewY,_=r.scaleX,p=r.scaleY,c=r.transformPerspective,m=r.force3D,g=r.target,v=r.zOrigin,y="",T="auto"===m&&t&&1!==t||!0===m;if(v&&(l!==er||h!==er)){var b,w=parseFloat(h)*Ie,x=Math.sin(w),k=Math.cos(w);w=parseFloat(l)*Ie,b=Math.cos(w),a=Td(g,a,x*b*-v),s=Td(g,s,-Math.sin(w)*-v),o=Td(g,o,k*b*-v+v)}c!==rr&&(y+="perspective("+c+ir),(i||n)&&(y+="translate("+i+"%, "+n+"%) "),!T&&a===rr&&s===rr&&o===rr||(y+=o!==rr||T?"translate3d("+a+", "+s+", "+o+") ":"translate("+a+", "+s+ir),u!==er&&(y+="rotate("+u+ir),h!==er&&(y+="rotateY("+h+ir),l!==er&&(y+="rotateX("+l+ir),f===er&&d===er||(y+="skew("+f+", "+d+ir),1===_&&1===p||(y+="scale("+_+", "+p+ir),g.style[Ve]=y||"translate(0, 0)"},ar=function _renderSVGTransforms(t,e){var r,i,n,a,s,o=e||this,u=o.xPercent,h=o.yPercent,l=o.x,f=o.y,d=o.rotation,_=o.skewX,p=o.skewY,c=o.scaleX,m=o.scaleY,g=o.target,v=o.xOrigin,y=o.yOrigin,T=o.xOffset,b=o.yOffset,w=o.forceCSS,x=parseFloat(l),k=parseFloat(f);d=parseFloat(d),_=parseFloat(_),(p=parseFloat(p))&&(_+=p=parseFloat(p),d+=p),d||_?(d*=Ie,_*=Ie,r=Math.cos(d)*c,i=Math.sin(d)*c,n=Math.sin(d-_)*-m,a=Math.cos(d-_)*m,_&&(p*=Ie,s=Math.tan(_-p),n*=s=Math.sqrt(1+s*s),a*=s,p&&(s=Math.tan(p),r*=s=Math.sqrt(1+s*s),i*=s)),r=ca(r),i=ca(i),n=ca(n),a=ca(a)):(r=c,a=m,i=n=0),(x&&!~(l+"").indexOf("px")||k&&!~(f+"").indexOf("px"))&&(x=Ed(g,"x",l,"px"),k=Ed(g,"y",f,"px")),(v||y||T||b)&&(x=ca(x+v-(v*r+y*n)+T),k=ca(k+y-(v*i+y*a)+b)),(u||h)&&(s=g.getBBox(),x=ca(x+u/100*s.width),k=ca(k+h/100*s.height)),s="matrix("+r+","+i+","+n+","+a+","+x+","+k+")",g.setAttribute("transform",s),w&&(g.style[Ve]=s)};ba("padding,margin,Width,Radius",function(e,r){var t="Right",i="Bottom",n="Left",o=(r<3?["Top",t,i,n]:["Top"+n,"Top"+t,i+t,i+n]).map(function(t){return r<2?e+t:"border"+t+e});Ge[1<r?"border"+e:e]=function(e,t,r,i,n){var a,s;if(arguments.length<4)return a=o.map(function(t){return Fd(e,t,r)}),5===(s=a.join(" ")).split(a[0]).length?a[0]:s;a=(i+"").split(" "),s={},o.forEach(function(t,e){return s[t]=a[e]=a[e]||a[(e-1)/2|0]}),e.init(t,s,n)}});var sr,or,ur,hr={name:"css",register:wd,targetTest:function targetTest(t){return t.style&&t.nodeType},init:function init(t,e,r,i,n){var a,s,u,h,l,f,d,_,p,c,m,g,v,y,T,b=this._props,w=t.style,x=r.vars.startAt;for(d in _e||wd(),e)if("autoRound"!==d&&(s=e[d],!ft[d]||!Ub(d,e,r,i,t,n)))if(l=typeof s,f=Ge[d],"function"===l&&(l=typeof(s=s.call(r,i,t,n))),"string"===l&&~s.indexOf("random(")&&(s=hb(s)),f)f(this,t,d,s,r)&&(T=1);else if("--"===d.substr(0,2))a=(getComputedStyle(t).getPropertyValue(d)+"").trim(),s+="",Ct.lastIndex=0,Ct.test(a)||(_=Ra(a),p=Ra(s)),p?_!==p&&(a=Ed(t,d,a,p)+p):_&&(s+=_),this.add(w,"setProperty",a,s,i,n,0,0,d),b.push(d);else if("undefined"!==l){if(x&&d in x?(a="function"==typeof x[d]?x[d].call(r,i,t,n):x[d],o(a)&&~a.indexOf("random(")&&(a=hb(a)),Ra(a+"")||(a+=U.units[d]||Ra(Fd(t,d))||""),"="===(a+"").charAt(1)&&(a=Fd(t,d))):a=Fd(t,d),h=parseFloat(a),(c="string"===l&&"="===s.charAt(1)&&s.substr(0,2))&&(s=s.substr(2)),u=parseFloat(s),d in qe&&("autoAlpha"===d&&(1===h&&"hidden"===Fd(t,"visibility")&&u&&(h=0),Cd(this,w,"visibility",h?"inherit":"hidden",u?"inherit":"hidden",!u)),"scale"!==d&&"transform"!==d&&~(d=qe[d]).indexOf(",")&&(d=d.split(",")[0])),m=d in Fe)if(g||((v=t._gsap).renderTransform&&!e.parseTransform||He(t,e.parseTransform),y=!1!==e.smoothOrigin&&v.smooth,(g=this._pt=new oe(this._pt,w,Ve,0,1,v.renderTransform,v,0,-1)).dep=1),"scale"===d)this._pt=new oe(this._pt,v,"scaleY",v.scaleY,(c?ea(v.scaleY,c+u):u)-v.scaleY||0),b.push("scaleY",d),d+="X";else{if("transformOrigin"===d){s=Id(s),v.svg?Qd(t,s,0,y,0,this):((p=parseFloat(s.split(" ")[2])||0)!==v.zOrigin&&Cd(this,v,"zOrigin",v.zOrigin,p),Cd(this,w,d,Je(a),Je(s)));continue}if("svgOrigin"===d){Qd(t,s,1,y,0,this);continue}if(d in Ze){$d(this,v,d,h,c?ea(h,c+s):s);continue}if("smoothOrigin"===d){Cd(this,v,"smooth",v.smooth,s);continue}if("force3D"===d){v[d]=s;continue}if("transform"===d){ae(this,s,t);continue}}else d in w||(d=je(d)||d);if(m||(u||0===u)&&(h||0===h)&&!Ye.test(s)&&d in w)u=u||0,(_=(a+"").substr((h+"").length))!==(p=Ra(s)||(d in U.units?U.units[d]:_))&&(h=Ed(t,d,a,p)),this._pt=new oe(this._pt,m?v:w,d,h,(c?ea(h,c+u):u)-h,m||"px"!==p&&"zIndex"!==d||!1===e.autoRound?dd:gd),this._pt.u=p||0,_!==p&&"%"!==p&&(this._pt.b=a,this._pt.r=fd);else if(d in w)Gd.call(this,t,d,a,c?c+s:s);else{if(!(d in t)){N(d,s);continue}this.add(t,d,a||t[d],c?c+s:s,i,n)}b.push(d)}T&&se(this)},get:Fd,aliases:qe,getSetter:function getSetter(t,e,i){var n=qe[e];return n&&n.indexOf(",")<0&&(e=n),e in Fe&&e!==Xe&&(t._gsap.x||Fd(t,"x"))?i&&ce===i?"scale"===e?md:ld:(ce=i||{})&&("scale"===e?nd:od):t.style&&!r(t.style[e])?jd:~e.indexOf("-")?kd:Ht(t,e)},core:{_removeProperty:Bd,_getMatrix:Pd}};he.utils.checkPrefix=je,ur=ba((sr="x,y,z,scale,scaleX,scaleY,xPercent,yPercent")+","+(or="rotation,rotationX,rotationY,skewX,skewY")+",transform,transformOrigin,svgOrigin,force3D,smoothOrigin,transformPerspective",function(t){Fe[t]=1}),ba(or,function(t){U.units[t]="deg",Ze[t]=1}),qe[ur[13]]=sr+","+or,ba("0:translateX,1:translateY,2:translateZ,8:rotate,8:rotationZ,8:rotateZ,9:rotateX,10:rotateY",function(t){var e=t.split(":");qe[e[1]]=ur[e[0]]}),ba("x,y,z,top,right,bottom,left,width,height,fontSize,padding,margin,perspective",function(t){U.units[t]="px"}),he.registerPlugin(hr);var lr=he.registerPlugin(hr)||he,fr=lr.core.Tween;e.Back=Ae,e.Bounce=Se,e.CSSPlugin=hr,e.Circ=Ee,e.Cubic=ke,e.Elastic=Ce,e.Expo=Re,e.Linear=we,e.Power0=ge,e.Power1=ve,e.Power2=ye,e.Power3=Te,e.Power4=be,e.Quad=xe,e.Quart=Oe,e.Quint=Pe,e.Sine=ze,e.SteppedEase=De,e.Strong=Me,e.TimelineLite=Ut,e.TimelineMax=Ut,e.TweenLite=$t,e.TweenMax=fr,e.default=lr,e.gsap=lr;if (typeof(window)==="undefined"||window!==e){Object.defineProperty(e,"__esModule",{value:!0})} else {delete e.default}});

/*
    panr - v0.0.1
    jQuery plugin for zoom & pan elements on mousemove
    by Robert Bue (@robert_bue)

    Powered by the Greensock Tweening Platform
    http://www.greensock.com
    Greensock License info at http://www.greensock.com/licensing/
    
    Dual licensed under MIT and GPL.
 */

; (function ($, window, document, undefined) {

    // Create the defaults once
    var pluginName = "panr",
        defaults = {
            sensitivity: 20,
            scale: false,
            scaleOnHover: true,
            scaleTo: 1.1,
            scaleDuration: .25,
            panY: true,
            panX: true,
            panDuration: 0,
            resetPanOnMouseLeave: false,
            onEnter: function () { },
            onLeave: function () { }
        };

    // The actual plugin constructor
    function Plugin(element, options) {
        this.element = element;
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.init();
    }

    Plugin.prototype = {
        init: function () {

            var settings = this.settings,
                target = $(this.element),
                w = target.width(),
                h = target.height(),
                targetWidth = target.width() - settings.sensitivity,
                cx = (w - targetWidth) / targetWidth,
                x,
                y,
                panVars,
                xPanVars,
                yPanVars,
                mouseleaveVars;

            if (settings.scale || (!settings.scaleOnHover && settings.scale)) {
                TweenMax.set(target.find('img, video, .wd-block-banner-img'), { scale: settings.scaleTo });
            }

            // moveTarget
            if (typeof settings.moveTarget === "string") {
                settings.moveTarget = $(this.element).parent(settings.moveTarget);
            }

            // If no target provided we'll use the hovered element
            if (!settings.moveTarget) {
                settings.moveTarget = $(this.element);
            }

            settings.moveTarget.on('mousemove', function (e) {
                x = e.pageX - target.offset().left; // mouse x coordinate relative to the container
                y = e.pageY - target.offset().top; // mouse x coordinate relative to the container

                if (settings.panX) {
                    xPanVars = { x: -cx * x };
                }

                if (settings.panY) {
                    yPanVars = { y: -cx * y };
                }

                panVars = $.extend({}, xPanVars, yPanVars);

                // Pan element
                TweenMax.to(target.find('img, video, .wd-block-banner-img'), settings.panDuration, panVars);
            });

            // On mouseover
            settings.moveTarget.on('mouseenter', function (e) {
                if (settings.scaleOnHover) {
                    // Scale up element
                    TweenMax.to(target.find('img, video, .wd-block-banner-img'), settings.scaleDuration, { scale: settings.scaleTo });
                }

                settings.onEnter(target);
            });

            if (!settings.scale || (!settings.scaleOnHover && !settings.scale)) {

                mouseleaveVars = { scale: 1, x: 0, y: 0 };

            } else {
                if (settings.resetPanOnMouseLeave) {
                    mouseleaveVars = { x: 0, y: 0 };
                }
            }

            settings.moveTarget.on('mouseleave', function (e) {
                // Reset element

                var newDuration = settings.scaleDuration + 0.2;

                TweenMax.to(target.find('img, .banner-image video, .wd-block-banner-img'), newDuration, mouseleaveVars);

                settings.onLeave(target);
            });
        }
    };

    $.fn[pluginName] = function (options) {
        return this.each(function () {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new Plugin(this, options));
            }
        });
    };

})(jQuery, window, document);


/*! Magnific Popup - v1.0.0 - 2015-01-03
* http://dimsemenov.com/plugins/magnific-popup/
* Copyright (c) 2015 Dmitry Semenov; */
;(function (factory) { 
if (typeof define === 'function' && define.amd) { 
 // AMD. Register as an anonymous module. 
 define(['jquery'], factory); 
 } else if (typeof exports === 'object') { 
 // Node/CommonJS 
 factory(require('jquery')); 
 } else { 
 // Browser globals 
 factory(window.jQuery || window.Zepto); 
 } 
 }(function($) { 

/*>>core*/
/**
 * 
 * Magnific Popup Core JS file
 * 
 */


/**
 * Private static constants
 */
var CLOSE_EVENT = 'Close',
	BEFORE_CLOSE_EVENT = 'BeforeClose',
	AFTER_CLOSE_EVENT = 'AfterClose',
	BEFORE_APPEND_EVENT = 'BeforeAppend',
	MARKUP_PARSE_EVENT = 'MarkupParse',
	OPEN_EVENT = 'Open',
	CHANGE_EVENT = 'Change',
	NS = 'mfp',
	EVENT_NS = '.' + NS,
	READY_CLASS = 'mfp-ready',
	REMOVING_CLASS = 'mfp-removing',
	PREVENT_CLOSE_CLASS = 'mfp-prevent-close';


/**
 * Private vars 
 */
/*jshint -W079 */
var mfp, // As we have only one instance of MagnificPopup object, we define it locally to not to use 'this'
	MagnificPopup = function(){},
	_isJQ = !!(window.jQuery),
	_prevStatus,
	_window = $(window),
	_document,
	_prevContentType,
	_wrapClasses,
	_currPopupType;


/**
 * Private functions
 */
var _mfpOn = function(name, f) {
		mfp.ev.on(NS + name + EVENT_NS, f);
	},
	_getEl = function(className, appendTo, html, raw) {
		var el = document.createElement('div');
		el.className = 'mfp-'+className;
		if(html) {
			el.innerHTML = html;
		}
		if(!raw) {
			el = $(el);
			if(appendTo) {
				el.appendTo(appendTo);
			}
		} else if(appendTo) {
			appendTo.appendChild(el);
		}
		return el;
	},
	_mfpTrigger = function(e, data) {
		mfp.ev.triggerHandler(NS + e, data);

		if(mfp.st.callbacks) {
			// converts "mfpEventName" to "eventName" callback and triggers it if it's present
			e = e.charAt(0).toLowerCase() + e.slice(1);
			if(mfp.st.callbacks[e]) {
				mfp.st.callbacks[e].apply(mfp, Array.isArray(data) ? data : [data]);
			}
		}
	},
	_getCloseBtn = function(type) {
		if(type !== _currPopupType || !mfp.currTemplate.closeBtn) {
			mfp.currTemplate.closeBtn = $( mfp.st.closeMarkup.replace('%title%', mfp.st.tClose ) );
			_currPopupType = type;
		}
		return mfp.currTemplate.closeBtn;
	},
	// Initialize Magnific Popup only when called at least once
	_checkInstance = function() {
		if(!$.magnificPopup.instance) {
			/*jshint -W020 */
			mfp = new MagnificPopup();
			mfp.init();
			$.magnificPopup.instance = mfp;
		}
	},
	// CSS transition detection, http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
	supportsTransitions = function() {
		var s = document.createElement('p').style, // 's' for style. better to create an element if body yet to exist
			v = ['ms','O','Moz','Webkit']; // 'v' for vendor

		if( s['transition'] !== undefined ) {
			return true; 
		}
			
		while( v.length ) {
			if( v.pop() + 'Transition' in s ) {
				return true;
			}
		}
				
		return false;
	};



/**
 * Public functions
 */
MagnificPopup.prototype = {

	constructor: MagnificPopup,

	/**
	 * Initializes Magnific Popup plugin. 
	 * This function is triggered only once when $.fn.magnificPopup or $.magnificPopup is executed
	 */
	init: function() {
		var appVersion = navigator.appVersion;
		mfp.isIE7 = appVersion.indexOf("MSIE 7.") !== -1; 
		mfp.isIE8 = appVersion.indexOf("MSIE 8.") !== -1;
		mfp.isLowIE = mfp.isIE7 || mfp.isIE8;
		mfp.isAndroid = (/android/gi).test(appVersion);
		mfp.isIOS = (/iphone|ipad|ipod/gi).test(appVersion);
		mfp.supportsTransition = supportsTransitions();

		// We disable fixed positioned lightbox on devices that don't handle it nicely.
		// If you know a better way of detecting this - let me know.
		mfp.probablyMobile = (mfp.isAndroid || mfp.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent) );
		_document = $(document);

		mfp.popupsCache = {};
	},

	/**
	 * Opens popup
	 * @param  data [description]
	 */
	open: function(data) {

		var i;

		if(data.isObj === false) { 
			// convert jQuery collection to array to avoid conflicts later
			mfp.items = data.items.toArray();

			mfp.index = 0;
			var items = data.items,
				item;
			for(i = 0; i < items.length; i++) {
				item = items[i];
				if(item.parsed) {
					item = item.el[0];
				}
				if(item === data.el[0]) {
					mfp.index = i;
					break;
				}
			}
		} else {
			mfp.items = Array.isArray(data.items) ? data.items : [data.items];
			mfp.index = data.index || 0;
		}

		// if popup is already opened - we just update the content
		if(mfp.isOpen) {
			mfp.updateItemHTML();
			return;
		}
		
		mfp.types = []; 
		_wrapClasses = ' wd-scroll';
		if(data.mainEl && data.mainEl.length) {
			mfp.ev = data.mainEl.eq(0);
		} else {
			mfp.ev = _document;
		}

		if(data.key) {
			if(!mfp.popupsCache[data.key]) {
				mfp.popupsCache[data.key] = {};
			}
			mfp.currTemplate = mfp.popupsCache[data.key];
		} else {
			mfp.currTemplate = {};
		}



		mfp.st = $.extend(true, {}, $.magnificPopup.defaults, data ); 
		mfp.fixedContentPos = mfp.st.fixedContentPos === 'auto' ? !mfp.probablyMobile : mfp.st.fixedContentPos;

		if(mfp.st.modal) {
			mfp.st.closeOnContentClick = false;
			mfp.st.closeOnBgClick = false;
			mfp.st.showCloseBtn = false;
			mfp.st.enableEscapeKey = false;
		}
		

		// Building markup
		// main containers are created only once
		if(!mfp.bgOverlay) {

			// Dark overlay
			mfp.bgOverlay = _getEl('bg').on('click'+EVENT_NS, function() {
				mfp.close();
			});

			mfp.wrap = _getEl('wrap').attr('tabindex', -1).on('click'+EVENT_NS, function(e) {
				if(mfp._checkIfClose(e.target)) {
					mfp.close();
				}
			})
			.on('click'+EVENT_NS, '.wd-popup-close', function(e) {
				mfp.close();
				e.preventDefault()
			})

			mfp.container = _getEl('container', mfp.wrap);
		}

		mfp.contentContainer = _getEl('content');
		if(mfp.st.preloader) {
			mfp.preloader = _getEl('preloader', mfp.container, mfp.st.tLoading);
		}


		// Initializing modules
		var modules = $.magnificPopup.modules;
		for(i = 0; i < modules.length; i++) {
			var n = modules[i];
			n = n.charAt(0).toUpperCase() + n.slice(1);
			mfp['init'+n].call(mfp);
		}
		_mfpTrigger('BeforeOpen');


		if(mfp.st.showCloseBtn) {
			// Close button
			if(!mfp.st.closeBtnInside) {
				mfp.wrap.prepend( _getCloseBtn() ); // replace button position
			} else {
				_mfpOn(MARKUP_PARSE_EVENT, function(e, template, values, item) {
					values.close_replaceWith = _getCloseBtn(item.type);
				});
				_wrapClasses += ' mfp-close-btn-in';
			}
		}

		if(mfp.st.alignTop) {
			_wrapClasses += ' mfp-align-top';
		}

	

		if(mfp.fixedContentPos) {
			// Now the overflow property is defined in the theme’s stylesheet
			// mfp.wrap.css({
			// 	overflow: mfp.st.overflowY,
			// 	overflowX: 'hidden',
			// 	overflowY: mfp.st.overflowY
			// });
		} else {
			mfp.wrap.css({ 
				top: _window.scrollTop(),
				position: 'absolute'
			});
		}
		if( mfp.st.fixedBgPos === false || (mfp.st.fixedBgPos === 'auto' && !mfp.fixedContentPos) ) {
			mfp.bgOverlay.css({
				height: _document.height(),
				position: 'absolute'
			});
		}

		

		if(mfp.st.enableEscapeKey) {
			// Close on ESC key
			_document.on('keyup' + EVENT_NS, function(e) {
				if(e.keyCode === 27) {
					mfp.close();
				}
			});
		}

		_window.on('resize' + EVENT_NS, function() {
			mfp.updateSize();
		});


		if(!mfp.st.closeOnContentClick) {
			_wrapClasses += ' mfp-auto-cursor';
		}
		
		if(_wrapClasses)
			mfp.wrap.addClass(_wrapClasses);


		// this triggers recalculation of layout, so we get it once to not to trigger twice
		var windowHeight = mfp.wH = _window.height();

		
		var windowStyles = {};

		// if( mfp.fixedContentPos ) {
        //     if(mfp._hasScrollBar(windowHeight)){
        //         var s = mfp._getScrollbarSize();
        //         if(s) {
        //             windowStyles.marginRight = s;
        //         }
        //     }
        // }

		if(mfp.fixedContentPos) {
			if(!mfp.isIE7) {
				windowStyles.overflow = 'hidden';
			} else {
				// ie7 double-scroll bug
				$('body, html').css('overflow', 'hidden');
			}
		}

		
		
		var classesToadd = mfp.st.mainClass;
		if(mfp.isIE7) {
			classesToadd += ' mfp-ie7';
		}
		if(classesToadd) {
			mfp._addClassToMFP( classesToadd );
		}

		// add content
		mfp.updateItemHTML();

		_mfpTrigger('BuildControls');

		// remove scrollbar, add margin e.t.c
		$('html').css(windowStyles);
		
		// add everything to DOM
		mfp.bgOverlay.add(mfp.wrap).prependTo( mfp.st.prependTo || $(document.body) );

		// Save last focused element
		mfp._lastFocusedEl = document.activeElement;
		
		// Wait for next cycle to allow CSS transition
		setTimeout(function() {
			
			if(mfp.content) {
				mfp._addClassToMFP(READY_CLASS);
				mfp._setFocus();
			} else {
				// if content is not defined (not loaded e.t.c) we add class only for BG
				mfp.bgOverlay.addClass(READY_CLASS);
			}
			
			// Trap the focus in popup
			_document.on('focusin' + EVENT_NS, mfp._onFocusIn);

		}, 16);

		// Woodmart: add wd-in class to popup wrap
		setTimeout(function() {
			if (mfp.wrap.find('.wd-popup').length) mfp.wrap.find('.wd-popup-wrap').addClass('wd-in wd-animated');
		}, 16);

		mfp.isOpen = true;
		mfp.updateSize(windowHeight);
		_mfpTrigger(OPEN_EVENT);

		return data;
	},

	/**
	 * Closes the popup
	 */
	close: function() {
		if(!mfp.isOpen) return;
		_mfpTrigger(BEFORE_CLOSE_EVENT);
		mfp.isOpen = false;
		// for CSS3 animation
		if(mfp.st.removalDelay && !mfp.isLowIE && mfp.supportsTransition )  {
			mfp._addClassToMFP(REMOVING_CLASS);
			// Out animation for woodmart popups
			if (mfp.wrap.find('.wd-popup').length) {
				mfp.wrap.find('.wd-popup-wrap').removeClass('wd-in')
				mfp.wrap.find('.wd-popup-wrap').addClass('wd-out');
			}
			setTimeout(function() {
				if (mfp.wrap.find('.wd-popup').length) {
					setTimeout(function() {
						mfp.wrap.find('.wd-popup-wrap').removeClass('wd-out')
					})
				}
				mfp._close();
			}, mfp.st.removalDelay);
		} else {
			mfp._close();
		}
	},

	/**
	 * Helper for close() function
	 */
	_close: function() {
		_mfpTrigger(CLOSE_EVENT);

		var classesToRemove = REMOVING_CLASS + ' ' + READY_CLASS + ' ';

		mfp.bgOverlay.detach();
		mfp.wrap.detach();
		mfp.container.empty();

		if(mfp.st.mainClass) {
			classesToRemove += mfp.st.mainClass + ' ';
		}

		mfp._removeClassFromMFP(classesToRemove);

		if(mfp.fixedContentPos) {
			var windowStyles = {marginRight: ''};
			if(mfp.isIE7) {
				$('body, html').css('overflow', '');
			} else {
				windowStyles.overflow = '';
			}
			$('html').css(windowStyles);
		}
		
		_document.off('keyup' + EVENT_NS + ' focusin' + EVENT_NS);
		mfp.ev.off(EVENT_NS);

		// clean up DOM elements that aren't removed
		mfp.wrap.attr('class', 'mfp-wrap').removeAttr('style');
		mfp.bgOverlay.attr('class', 'mfp-bg');
		mfp.container.attr('class', 'mfp-container');

		// remove close button from target element
		if(mfp.st.showCloseBtn &&
		(!mfp.st.closeBtnInside || mfp.currTemplate[mfp.currItem.type] === true)) {
			if(mfp.currTemplate.closeBtn)
				mfp.currTemplate.closeBtn.detach();
		}

		// if(mfp._lastFocusedEl) {
		// 	$(mfp._lastFocusedEl).focus(); // put tab focus back
		// }
		mfp.currItem = null;	
		mfp.content = null;
		mfp.currTemplate = null;
		mfp.prevHeight = 0;

		_mfpTrigger(AFTER_CLOSE_EVENT);
	},
	
	updateSize: function(winHeight) {

		if(mfp.isIOS) {
			// fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2
			var zoomLevel = document.documentElement.clientWidth / window.innerWidth;
			var height = window.innerHeight * zoomLevel;
			mfp.wrap.css('height', height);
			mfp.wH = height;
		} else {
			mfp.wH = winHeight || _window.height();
		}
		// Fixes #84: popup incorrectly positioned with position:relative on body
		if(!mfp.fixedContentPos) {
			mfp.wrap.css('height', mfp.wH);
		}

		_mfpTrigger('Resize');

	},

	/**
	 * Set content of popup based on current index
	 */
	updateItemHTML: function() {
		var item = mfp.items[mfp.index];

		// Detach and perform modifications
		mfp.contentContainer.detach();

		if(mfp.content)
			mfp.content.detach();

		if(!item.parsed) {
			item = mfp.parseEl( mfp.index );
		}

		var type = item.type;	

		_mfpTrigger('BeforeChange', [mfp.currItem ? mfp.currItem.type : '', type]);
		// BeforeChange event works like so:
		// _mfpOn('BeforeChange', function(e, prevType, newType) { });
		
		mfp.currItem = item;

		

		

		if(!mfp.currTemplate[type]) {
			var markup = mfp.st[type] ? mfp.st[type].markup : false;

			// allows to modify markup
			_mfpTrigger('FirstMarkupParse', markup);

			if(markup) {
				mfp.currTemplate[type] = $(markup);
			} else {
				// if there is no markup found we just define that template is parsed
				mfp.currTemplate[type] = true;
			}
		}

		if(_prevContentType && _prevContentType !== item.type) {
			mfp.container.removeClass('mfp-'+_prevContentType+'-holder');
		}
		
		var newContent = mfp['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, mfp.currTemplate[type]);
		mfp.appendContent(newContent, type);

		item.preloaded = true;

		_mfpTrigger(CHANGE_EVENT, item);
		_prevContentType = item.type;
		
		// Append container back after its content changed
		mfp.container.prepend(mfp.contentContainer);

		_mfpTrigger('AfterChange');
	},


	/**
	 * Set HTML content of popup
	 */
	appendContent: function(newContent, type) {
		mfp.content = newContent;
		
		if(newContent) {
			if(mfp.st.showCloseBtn && mfp.st.closeBtnInside &&
				mfp.currTemplate[type] === true) {
				// if there is no markup, we just append close button element inside
				if(!mfp.contentContainer.find('.mfp-close, .wd-popup-close').length) {
					mfp.contentContainer.prepend(_getCloseBtn()); // replace button position
				}
			} else {
				mfp.content = newContent;
			}
		} else {
			mfp.content = '';
		}

		_mfpTrigger(BEFORE_APPEND_EVENT);
		mfp.container.addClass('mfp-'+type+'-holder');

		// WoodMart: wrap close button + content into .wd-popup-wrap when .wd-popup present
		if (newContent) {
			var $wdPopup = newContent.hasClass('wd-popup') ? newContent : newContent.find('.wd-popup')

			if ($wdPopup.length) {
				var $wrap = $('<div class="wd-popup-wrap wd-animation wd-transform wd-animation-ready"></div>')
				if (!$wdPopup.hasClass('wd-popup-builder') && !$wdPopup.hasClass('wd-promo-popup')) {
					$wrap.addClass('wd-animation-default')
				}
				var $close = mfp.contentContainer.find('.wd-popup-close')
				if (!$close.length) $close = newContent.find('.wd-popup-close')

				mfp.contentContainer.empty()
				if ($close.length) $wrap.append($close)
				$wrap.append(newContent)

				mfp.content = $wdPopup
				mfp.contentContainer.append($wrap)
				return
			}
		}

		mfp.contentContainer.append(mfp.content);
	},



	
	/**
	 * Creates Magnific Popup data object based on given data
	 * @param  {int} index Index of item to parse
	 */
	parseEl: function(index) {
		var item = mfp.items[index],
			type;

		if(item.tagName) {
			item = { el: $(item) };
		} else {
			type = item.type;
			item = { data: item, src: item.src };
		}

		if(item.el) {
			var types = mfp.types;

			// check for 'mfp-TYPE' class
			for(var i = 0; i < types.length; i++) {
				if( item.el.hasClass('mfp-'+types[i]) ) {
					type = types[i];
					break;
				}
			}

			item.src = item.el.attr('data-mfp-src');
			if(!item.src) {
				item.src = item.el.attr('href');
			}
		}

		item.type = type || mfp.st.type || 'inline';
		item.index = index;
		item.parsed = true;
		mfp.items[index] = item;
		_mfpTrigger('ElementParse', item);

		return mfp.items[index];
	},


	/**
	 * Initializes single popup or a group of popups
	 */
	addGroup: function(el, options) {
		var eHandler = function(e) {
			e.mfpEl = this;
			mfp._openClick(e, el, options);
		};

		if(!options) {
			options = {};
		} 

		var eName = 'click.magnificPopup';
		options.mainEl = el;
		
		if(options.items) {
			options.isObj = true;
			el.off(eName).on(eName, eHandler);
		} else {
			options.isObj = false;
			if(options.delegate) {
				el.off(eName).on(eName, options.delegate , eHandler);
			} else {
				options.items = el;
				el.off(eName).on(eName, eHandler);
			}
		}
	},
	_openClick: function(e, el, options) {
		var midClick = options.midClick !== undefined ? options.midClick : $.magnificPopup.defaults.midClick;


		if(!midClick && ( e.which === 2 || e.ctrlKey || e.metaKey ) ) {
			return;
		}

		var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn;

		if(disableOn) {
			if(typeof disableOn === "function") {
				if( !disableOn.call(mfp) ) {
					return true;
				}
			} else { // else it's number
				if( _window.width() < disableOn ) {
					return true;
				}
			}
		}
		
		if(e.type) {
			e.preventDefault();

			// This will prevent popup from closing if element is inside and popup is already opened
			if(mfp.isOpen) {
				e.stopPropagation();
			}
		}
			

		options.el = $(e.mfpEl);
		if(options.delegate) {
			options.items = el.find(options.delegate);
		}
		mfp.open(options);
	},


	/**
	 * Updates text on preloader
	 */
	updateStatus: function(status, text) {

		if(mfp.preloader) {
			if(_prevStatus !== status) {
				mfp.container.removeClass('mfp-s-'+_prevStatus);
			}

			if(!text && status === 'loading') {
				text = mfp.st.tLoading;
			}

			var data = {
				status: status,
				text: text
			};
			// allows to modify status
			_mfpTrigger('UpdateStatus', data);

			status = data.status;
			text = data.text;

			mfp.preloader.html(text);

			mfp.preloader.find('a').on('click', function(e) {
				e.stopImmediatePropagation();
			});

			mfp.container.addClass('mfp-s-'+status);
			_prevStatus = status;
		}
	},


	/*
		"Private" helpers that aren't private at all
	 */
	// Check to close popup or not
	// "target" is an element that was clicked
	_checkIfClose: function(target) {

		if($(target).hasClass(PREVENT_CLOSE_CLASS)) {
			return;
		}

		var closeOnContent = mfp.st.closeOnContentClick;
		var closeOnBg = mfp.st.closeOnBgClick;

		if(closeOnContent && closeOnBg) {
			return true;
		} else {

			// We close the popup if click is on close button or on preloader. Or if there is no content.
			if(!mfp.content || $(target).hasClass('mfp-close') || $(target).hasClass('wd-popup-close') || (mfp.preloader && target === mfp.preloader[0]) ) {
				return true;
			}

			// if click is outside the content
			if(  (target !== mfp.content[0] && !$.contains(mfp.content[0], target))  ) {
				if(closeOnBg) {
					// last check, if the clicked element is in DOM, (in case it's removed onclick)
					if( $.contains(document, target) ) {
						return true;
					}
				}
			} else if(closeOnContent) {
				return true;
			}

		}
		return false;
	},
	_addClassToMFP: function(cName) {
		mfp.bgOverlay.addClass(cName);
		mfp.wrap.addClass(cName);
	},
	_removeClassFromMFP: function(cName) {
		this.bgOverlay.removeClass(cName);
		mfp.wrap.removeClass(cName);
	},
	_hasScrollBar: function(winHeight) {
		return (  (mfp.isIE7 ? _document.height() : document.body.scrollHeight) > (winHeight || _window.height()) );
	},
	_setFocus: function() {
		(mfp.st.focus ? mfp.content.find(mfp.st.focus).eq(0) : mfp.wrap).trigger('focus');
	},
	_onFocusIn: function(e) {
		if( e.target !== mfp.wrap[0] && !$.contains(mfp.wrap[0], e.target) ) {
			mfp._setFocus();
			return false;
		}
	},
	_parseMarkup: function(template, values, item) {
		var arr;
		if(item.data) {
			values = $.extend(item.data, values);
		}
		_mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item] );

		$.each(values, function(key, value) {
			if(value === undefined || value === false) {
				return true;
			}
			arr = key.split('_');
			if(arr.length > 1) {
				var el = template.find(EVENT_NS + '-'+arr[0]);

				if(el.length > 0) {
					var attr = arr[1];
					if(attr === 'replaceWith') {
						if(el[0] !== value[0]) {
							el.replaceWith(value);
						}
					} else if(attr === 'img') {
						if(el.is('img')) {
							el.attr('src', value);
						} else {
							el.replaceWith( '<img src="'+value+'" class="' + el.attr('class') + '" />' );
						}
					} else {
						el.attr(arr[1], value);
					}
				}

			} else {
				template.find(EVENT_NS + '-'+key).html(value);
			}
		});
	},

	_getScrollbarSize: function() {
		// thx David
		if(mfp.scrollbarSize === undefined) {
			var scrollDiv = document.createElement("div");
			scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
			document.body.appendChild(scrollDiv);
			mfp.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
			document.body.removeChild(scrollDiv);
		}
		return mfp.scrollbarSize;
	}

}; /* MagnificPopup core prototype end */




/**
 * Public static functions
 */
$.magnificPopup = {
	instance: null,
	proto: MagnificPopup.prototype,
	modules: [],

	open: function(options, index) {
		_checkInstance();	

		if(!options) {
			options = {};
		} else {
			options = $.extend(true, {}, options);
		}
			

		options.isObj = true;
		options.index = index || 0;
		return this.instance.open(options);
	},

	close: function() {
		return $.magnificPopup.instance && $.magnificPopup.instance.close();
	},

	registerModule: function(name, module) {
		if(module.options) {
			$.magnificPopup.defaults[name] = module.options;
		}
		$.extend(this.proto, module.proto);			
		this.modules.push(name);
	},

	defaults: {   

		// Info about options is in docs:
		// http://dimsemenov.com/plugins/magnific-popup/documentation.html#options
		
		disableOn: 0,	

		key: null,

		midClick: false,

		mainClass: '',

		preloader: true,

		focus: '', // CSS selector of input to focus after popup is opened
		
		closeOnContentClick: false,

		closeOnBgClick: true,

		closeBtnInside: true, 

		showCloseBtn: true,

		enableEscapeKey: true,

		modal: false,

		alignTop: false,
	
		removalDelay: 0,

		prependTo: null,
		
		fixedContentPos: 'auto', 
	
		fixedBgPos: 'auto',

		overflowY: 'auto',

		closeMarkup: '<button title="%title%" type="button" class="mfp-close">&times;</button>',

		tClose: 'Close (Esc)',

		tLoading: 'Loading...'

	}
};



$.fn.magnificPopup = function(options) {
	_checkInstance();

	var jqEl = $(this);

	// We call some API method of first param is a string
	if (typeof options === "string" ) {

		if(options === 'open') {
			var items,
				itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup,
				index = parseInt(arguments[1], 10) || 0;

			if(itemOpts.items) {
				items = itemOpts.items[index];
			} else {
				items = jqEl;
				if(itemOpts.delegate) {
					items = items.find(itemOpts.delegate);
				}
				items = items.eq( index );
			}
			mfp._openClick({mfpEl:items}, jqEl, itemOpts);
		} else {
			if(mfp.isOpen)
				mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1));
		}

	} else {
		// clone options obj
		options = $.extend(true, {}, options);
		
		/*
		 * As Zepto doesn't support .data() method for objects 
		 * and it works only in normal browsers
		 * we assign "options" object directly to the DOM element. FTW!
		 */
		if(_isJQ) {
			jqEl.data('magnificPopup', options);
		} else {
			jqEl[0].magnificPopup = options;
		}

		mfp.addGroup(jqEl, options);

	}
	return jqEl;
};


//Quick benchmark
/*
var start = performance.now(),
	i,
	rounds = 1000;

for(i = 0; i < rounds; i++) {

}
console.log('Test #1:', performance.now() - start);

start = performance.now();
for(i = 0; i < rounds; i++) {

}
console.log('Test #2:', performance.now() - start);
*/


/*>>core*/

/*>>inline*/

var INLINE_NS = 'inline',
	_hiddenClass,
	_inlinePlaceholder, 
	_lastInlineElement,
	_putInlineElementsBack = function() {
		if(_lastInlineElement) {
			_inlinePlaceholder.after( _lastInlineElement.addClass(_hiddenClass) ).detach();
			_lastInlineElement = null;
		}
	};

$.magnificPopup.registerModule(INLINE_NS, {
	options: {
		hiddenClass: 'hide', // will be appended with `mfp-` prefix
		markup: '',
		tNotFound: 'Content not found'
	},
	proto: {

		initInline: function() {
			mfp.types.push(INLINE_NS);

			_mfpOn(CLOSE_EVENT+'.'+INLINE_NS, function() {
				_putInlineElementsBack();
			});
		},

		getInline: function(item, template) {

			_putInlineElementsBack();

			if(item.src) {
				var inlineSt = mfp.st.inline,
					el = $(item.src);

				if(el.length) {

					// If target element has parent - we replace it with placeholder and put it back after popup is closed
					var parent = el[0].parentNode;
					if(parent && parent.tagName) {
						if(!_inlinePlaceholder) {
							_hiddenClass = inlineSt.hiddenClass;
							_inlinePlaceholder = _getEl(_hiddenClass);
							_hiddenClass = 'mfp-'+_hiddenClass;
						}
						// replace target inline element with placeholder
						_lastInlineElement = el.after(_inlinePlaceholder).detach().removeClass(_hiddenClass);
					}

					mfp.updateStatus('ready');
				} else {
					mfp.updateStatus('error', inlineSt.tNotFound);
					el = $('<div>');
				}

				item.inlineElement = el;
				return el;
			}

			mfp.updateStatus('ready');
			mfp._parseMarkup(template, {}, item);
			return template;
		}
	}
});

/*>>inline*/

/*>>ajax*/
var AJAX_NS = 'ajax',
	_ajaxCur,
	_removeAjaxCursor = function() {
		if(_ajaxCur) {
			$(document.body).removeClass(_ajaxCur);
		}
	},
	_destroyAjaxRequest = function() {
		_removeAjaxCursor();
		if(mfp.req) {
			mfp.req.abort();
		}
	};

$.magnificPopup.registerModule(AJAX_NS, {

	options: {
		settings: null,
		cursor: 'mfp-ajax-cur',
		tError: '<a href="%url%">The content</a> could not be loaded.'
	},

	proto: {
		initAjax: function() {
			mfp.types.push(AJAX_NS);
			_ajaxCur = mfp.st.ajax.cursor;

			_mfpOn(CLOSE_EVENT+'.'+AJAX_NS, _destroyAjaxRequest);
			_mfpOn('BeforeChange.' + AJAX_NS, _destroyAjaxRequest);
		},
		getAjax: function(item) {

			if(_ajaxCur) {
				$(document.body).addClass(_ajaxCur);
			}

			mfp.updateStatus('loading');

			var opts = $.extend({
				url: item.src,
				success: function(data, textStatus, jqXHR) {
					var temp = {
						data:data,
						xhr:jqXHR
					};

					_mfpTrigger('ParseAjax', temp);

					mfp.appendContent( $(temp.data), AJAX_NS );

					item.finished = true;

					_removeAjaxCursor();

					mfp._setFocus();

					setTimeout(function() {
						mfp.wrap.addClass(READY_CLASS);
					}, 16);

					mfp.updateStatus('ready');

					_mfpTrigger('AjaxContentAdded');
				},
				error: function() {
					_removeAjaxCursor();
					item.finished = item.loadError = true;
					mfp.updateStatus('error', mfp.st.ajax.tError.replace('%url%', item.src));
				}
			}, mfp.st.ajax.settings);

			mfp.req = $.ajax(opts);

			return '';
		}
	}
});





	

/*>>ajax*/

/*>>image*/
var _imgInterval,
	_getTitle = function(item) {
		if(item.data && item.data.title !== undefined) 
			return item.data.title;

		var src = mfp.st.image.titleSrc;

		if(src) {
			if(typeof src === "function") {
				return src.call(mfp, item);
			} else if(item.el) {
				return item.el.attr(src) || '';
			}
		}
		return '';
	};

$.magnificPopup.registerModule('image', {

	options: {
		markup: '<div class="mfp-figure">'+
					'<div class="mfp-close"></div>'+
					'<figure>'+
						'<div class="mfp-img"></div>'+
						'<figcaption>'+
							'<div class="mfp-bottom-bar">'+
								'<div class="mfp-title"></div>'+
								'<div class="mfp-counter"></div>'+
							'</div>'+
						'</figcaption>'+
					'</figure>'+
				'</div>',
		cursor: 'mfp-zoom-out-cur',
		titleSrc: 'title', 
		verticalFit: true,
		tError: '<a href="%url%">The image</a> could not be loaded.'
	},

	proto: {
		initImage: function() {
			var imgSt = mfp.st.image,
				ns = '.image';

			mfp.types.push('image');

			_mfpOn(OPEN_EVENT+ns, function() {
				if(mfp.currItem.type === 'image' && imgSt.cursor) {
					$(document.body).addClass(imgSt.cursor);
				}
			});

			_mfpOn(CLOSE_EVENT+ns, function() {
				if(imgSt.cursor) {
					$(document.body).removeClass(imgSt.cursor);
				}
				_window.off('resize' + EVENT_NS);
			});

			_mfpOn('Resize'+ns, mfp.resizeImage);
			if(mfp.isLowIE) {
				_mfpOn('AfterChange', mfp.resizeImage);
			}
		},
		resizeImage: function() {
			var item = mfp.currItem;
			if(!item || !item.img) return;

			if(mfp.st.image.verticalFit) {
				var decr = 0;
				// fix box-sizing in ie7/8
				if(mfp.isLowIE) {
					decr = parseInt(item.img.css('padding-top'), 10) + parseInt(item.img.css('padding-bottom'),10);
				}
				item.img.css('max-height', mfp.wH-decr);
			}
		},
		_onImageHasSize: function(item) {
			if(item.img) {
				
				item.hasSize = true;

				if(_imgInterval) {
					clearInterval(_imgInterval);
				}
				
				item.isCheckingImgSize = false;

				_mfpTrigger('ImageHasSize', item);

				if(item.imgHidden) {
					if(mfp.content)
						mfp.content.removeClass('mfp-loading');
					
					item.imgHidden = false;
				}

			}
		},

		/**
		 * Function that loops until the image has size to display elements that rely on it asap
		 */
		findImageSize: function(item) {

			var counter = 0,
				img = item.img[0],
				mfpSetInterval = function(delay) {

					if(_imgInterval) {
						clearInterval(_imgInterval);
					}
					// decelerating interval that checks for size of an image
					_imgInterval = setInterval(function() {
						if(img.naturalWidth > 0) {
							mfp._onImageHasSize(item);
							return;
						}

						if(counter > 200) {
							clearInterval(_imgInterval);
						}

						counter++;
						if(counter === 3) {
							mfpSetInterval(10);
						} else if(counter === 40) {
							mfpSetInterval(50);
						} else if(counter === 100) {
							mfpSetInterval(500);
						}
					}, delay);
				};

			mfpSetInterval(1);
		},

		getImage: function(item, template) {

			var guard = 0,

				// image load complete handler
				onLoadComplete = function() {
					if(item) {
						if (item.img[0].complete) {
							item.img.off('.mfploader');
							
							if(item === mfp.currItem){
								mfp._onImageHasSize(item);

								mfp.updateStatus('ready');
							}

							item.hasSize = true;
							item.loaded = true;

							_mfpTrigger('ImageLoadComplete');
							
						}
						else {
							// if image complete check fails 200 times (20 sec), we assume that there was an error.
							guard++;
							if(guard < 200) {
								setTimeout(onLoadComplete,100);
							} else {
								onLoadError();
							}
						}
					}
				},

				// image error handler
				onLoadError = function() {
					if(item) {
						item.img.off('.mfploader');
						if(item === mfp.currItem){
							mfp._onImageHasSize(item);
							mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
						}

						item.hasSize = true;
						item.loaded = true;
						item.loadError = true;
					}
				},
				imgSt = mfp.st.image;


			var el = template.find('.mfp-img');
			if(el.length) {
				var img = document.createElement('img');
				img.className = 'mfp-img';
				if(item.el && item.el.find('img').length) {
					img.alt = item.el.find('img').attr('alt');
				}
				item.img = $(img).on('load.mfploader', onLoadComplete).on('error.mfploader', onLoadError);
				img.src = item.src;

				// without clone() "error" event is not firing when IMG is replaced by new IMG
				// TODO: find a way to avoid such cloning
				if(el.is('img')) {
					item.img = item.img.clone();
				}

				img = item.img[0];
				if(img.naturalWidth > 0) {
					item.hasSize = true;
				} else if(!img.width) {										
					item.hasSize = false;
				}
			}

			mfp._parseMarkup(template, {
				title: _getTitle(item),
				img_replaceWith: item.img
			}, item);

			mfp.resizeImage();

			if(item.hasSize) {
				if(_imgInterval) clearInterval(_imgInterval);

				if(item.loadError) {
					template.addClass('mfp-loading');
					mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
				} else {
					template.removeClass('mfp-loading');
					mfp.updateStatus('ready');
				}
				return template;
			}

			mfp.updateStatus('loading');
			item.loading = true;

			if(!item.hasSize) {
				item.imgHidden = true;
				template.addClass('mfp-loading');
				mfp.findImageSize(item);
			} 

			return template;
		}
	}
});



/*>>image*/

/*>>zoom*/
var hasMozTransform,
	getHasMozTransform = function() {
		if(hasMozTransform === undefined) {
			hasMozTransform = document.createElement('p').style.MozTransform !== undefined;
		}
		return hasMozTransform;		
	};

$.magnificPopup.registerModule('zoom', {

	options: {
		enabled: false,
		easing: 'ease-in-out',
		duration: 300,
		opener: function(element) {
			return element.is('img') ? element : element.find('img');
		}
	},

	proto: {

		initZoom: function() {
			var zoomSt = mfp.st.zoom,
				ns = '.zoom',
				image;
				
			if(!zoomSt.enabled || !mfp.supportsTransition) {
				return;
			}

			var duration = zoomSt.duration,
				getElToAnimate = function(image) {
					var newImg = image.clone().removeAttr('style').removeAttr('class').addClass('mfp-animated-image'),
						transition = 'all '+(zoomSt.duration/1000)+'s ' + zoomSt.easing,
						cssObj = {
							position: 'fixed',
							zIndex: 9999,
							left: 0,
							top: 0,
							'-webkit-backface-visibility': 'hidden'
						},
						t = 'transition';

					cssObj['-webkit-'+t] = cssObj['-moz-'+t] = cssObj['-o-'+t] = cssObj[t] = transition;

					newImg.css(cssObj);
					return newImg;
				},
				showMainContent = function() {
					mfp.content.css('visibility', 'visible');
				},
				openTimeout,
				animatedImg;

			_mfpOn('BuildControls'+ns, function() {
				if(mfp._allowZoom()) {

					clearTimeout(openTimeout);
					mfp.content.css('visibility', 'hidden');

					// Basically, all code below does is clones existing image, puts in on top of the current one and animated it
					
					image = mfp._getItemToZoom();

					if(!image) {
						showMainContent();
						return;
					}

					animatedImg = getElToAnimate(image); 
					
					animatedImg.css( mfp._getOffset() );

					mfp.wrap.append(animatedImg);

					openTimeout = setTimeout(function() {
						animatedImg.css( mfp._getOffset( true ) );
						openTimeout = setTimeout(function() {

							showMainContent();

							setTimeout(function() {
								animatedImg.remove();
								image = animatedImg = null;
								_mfpTrigger('ZoomAnimationEnded');
							}, 16); // avoid blink when switching images 

						}, duration); // this timeout equals animation duration

					}, 16); // by adding this timeout we avoid short glitch at the beginning of animation


					// Lots of timeouts...
				}
			});
			_mfpOn(BEFORE_CLOSE_EVENT+ns, function() {
				if(mfp._allowZoom()) {

					clearTimeout(openTimeout);

					mfp.st.removalDelay = duration;

					if(!image) {
						image = mfp._getItemToZoom();
						if(!image) {
							return;
						}
						animatedImg = getElToAnimate(image);
					}
					
					
					animatedImg.css( mfp._getOffset(true) );
					mfp.wrap.append(animatedImg);
					mfp.content.css('visibility', 'hidden');
					
					setTimeout(function() {
						animatedImg.css( mfp._getOffset() );
					}, 16);
				}

			});

			_mfpOn(CLOSE_EVENT+ns, function() {
				if(mfp._allowZoom()) {
					showMainContent();
					if(animatedImg) {
						animatedImg.remove();
					}
					image = null;
				}	
			});
		},

		_allowZoom: function() {
			return mfp.currItem.type === 'image';
		},

		_getItemToZoom: function() {
			if(mfp.currItem.hasSize) {
				return mfp.currItem.img;
			} else {
				return false;
			}
		},

		// Get element postion relative to viewport
		_getOffset: function(isLarge) {
			var el;
			if(isLarge) {
				el = mfp.currItem.img;
			} else {
				el = mfp.st.zoom.opener(mfp.currItem.el || mfp.currItem);
			}

			var offset = el.offset();
			var paddingTop = parseInt(el.css('padding-top'),10);
			var paddingBottom = parseInt(el.css('padding-bottom'),10);
			offset.top -= ( $(window).scrollTop() - paddingTop );


			/*
			
			Animating left + top + width/height looks glitchy in Firefox, but perfect in Chrome. And vice-versa.

			 */
			var obj = {
				width: el.width(),
				// fix Zepto height+padding issue
				height: (_isJQ ? el.innerHeight() : el[0].offsetHeight) - paddingBottom - paddingTop
			};

			// I hate to do this, but there is no another option
			if( getHasMozTransform() ) {
				obj['-moz-transform'] = obj['transform'] = 'translate(' + offset.left + 'px,' + offset.top + 'px)';
			} else {
				obj.left = offset.left;
				obj.top = offset.top;
			}
			return obj;
		}

	}
});



/*>>zoom*/

/*>>iframe*/

var IFRAME_NS = 'iframe',
	_emptyPage = '//about:blank',
	
	_fixIframeBugs = function(isShowing) {
		if(mfp.currTemplate[IFRAME_NS]) {
			var el = mfp.currTemplate[IFRAME_NS].find('iframe');
			if(el.length) { 
				// reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug
				if(!isShowing) {
					el[0].src = _emptyPage;
				}

				// IE8 black screen bug fix
				if(mfp.isIE8) {
					el.css('display', isShowing ? 'block' : 'none');
				}
			}
		}
	};

$.magnificPopup.registerModule(IFRAME_NS, {

	options: {
		markup: '<div class="mfp-iframe-scaler">'+
					'<div class="mfp-close"></div>'+
					'<iframe class="mfp-iframe" src="//about:blank" frameborder="0" allowfullscreen></iframe>'+
				'</div>',

		srcAction: 'iframe_src',

		// we don't care and support only one default type of URL by default
		patterns: {
			youtube: {
				index: 'youtube.com', 
				id: 'v=', 
				src: '//www.youtube.com/embed/%id%?autoplay=1'
			},
			vimeo: {
				index: 'vimeo.com/',
				id: '/',
				src: '//player.vimeo.com/video/%id%?autoplay=1'
			},
			gmaps: {
				index: '//maps.google.',
				src: '%id%&output=embed'
			}
		}
	},

	proto: {
		initIframe: function() {
			mfp.types.push(IFRAME_NS);

			_mfpOn('BeforeChange', function(e, prevType, newType) {
				if(prevType !== newType) {
					if(prevType === IFRAME_NS) {
						_fixIframeBugs(); // iframe if removed
					} else if(newType === IFRAME_NS) {
						_fixIframeBugs(true); // iframe is showing
					} 
				}// else {
					// iframe source is switched, don't do anything
				//}
			});

			_mfpOn(CLOSE_EVENT + '.' + IFRAME_NS, function() {
				_fixIframeBugs();
			});
		},

		getIframe: function(item, template) {
			var embedSrc = item.src;
			var iframeSt = mfp.st.iframe;
				
			$.each(iframeSt.patterns, function() {
				if(embedSrc.indexOf( this.index ) > -1) {
					if(this.id) {
						if(typeof this.id === 'string') {
							embedSrc = embedSrc.substr(embedSrc.lastIndexOf(this.id)+this.id.length, embedSrc.length);
						} else {
							embedSrc = this.id.call( this, embedSrc );
						}
					}
					embedSrc = this.src.replace('%id%', embedSrc );
					return false; // break;
				}
			});
			
			var dataObj = {};
			if(iframeSt.srcAction) {
				dataObj[iframeSt.srcAction] = embedSrc;
			}
			mfp._parseMarkup(template, dataObj, item);

			mfp.updateStatus('ready');

			return template;
		}
	}
});



/*>>iframe*/

/*>>gallery*/
/**
 * Get looped index depending on number of slides
 */
var _getLoopedId = function(index) {
		var numSlides = mfp.items.length;
		if(index > numSlides - 1) {
			return index - numSlides;
		} else  if(index < 0) {
			return numSlides + index;
		}
		return index;
	},
	_replaceCurrTotal = function(text, curr, total) {
		return text.replace(/%curr%/gi, curr + 1).replace(/%total%/gi, total);
	};

$.magnificPopup.registerModule('gallery', {

	options: {
		enabled: false,
		arrowMarkup: '<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>',
		preload: [0,2],
		navigateByImgClick: true,
		arrows: true,

		tPrev: 'Previous (Left arrow key)',
		tNext: 'Next (Right arrow key)',
		tCounter: '%curr% of %total%'
	},

	proto: {
		initGallery: function() {

			var gSt = mfp.st.gallery,
				ns = '.mfp-gallery',
				supportsFastClick = Boolean($.fn.mfpFastClick);

			mfp.direction = true; // true - next, false - prev
			
			if(!gSt || !gSt.enabled ) return false;

			_wrapClasses += ' mfp-gallery';

			_mfpOn(OPEN_EVENT+ns, function() {

				if(gSt.navigateByImgClick) {
					mfp.wrap.on('click'+ns, '.mfp-img', function() {
						if(mfp.items.length > 1) {
							mfp.next();
							return false;
						}
					});
				}

				_document.on('keydown'+ns, function(e) {
					if (e.keyCode === 37) {
						mfp.prev();
					} else if (e.keyCode === 39) {
						mfp.next();
					}
				});
			});

			_mfpOn('UpdateStatus'+ns, function(e, data) {
				if(data.text) {
					data.text = _replaceCurrTotal(data.text, mfp.currItem.index, mfp.items.length);
				}
			});

			_mfpOn(MARKUP_PARSE_EVENT+ns, function(e, element, values, item) {
				var l = mfp.items.length;
				values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : '';
			});

			_mfpOn('BuildControls' + ns, function() {
				if(mfp.items.length > 1 && gSt.arrows && !mfp.arrowLeft) {
					var markup = gSt.arrowMarkup,
						arrowLeft = mfp.arrowLeft = $( markup.replace(/%title%/gi, gSt.tPrev).replace(/%dir%/gi, 'left') ).addClass(PREVENT_CLOSE_CLASS),			
						arrowRight = mfp.arrowRight = $( markup.replace(/%title%/gi, gSt.tNext).replace(/%dir%/gi, 'right') ).addClass(PREVENT_CLOSE_CLASS);

					var eName = supportsFastClick ? 'mfpFastClick' : 'click';
					arrowLeft[eName](function() {
						mfp.prev();
					});			
					arrowRight[eName](function() {
						mfp.next();
					});	

					// Polyfill for :before and :after (adds elements with classes mfp-a and mfp-b)
					if(mfp.isIE7) {
						_getEl('b', arrowLeft[0], false, true);
						_getEl('a', arrowLeft[0], false, true);
						_getEl('b', arrowRight[0], false, true);
						_getEl('a', arrowRight[0], false, true);
					}

					mfp.container.append(arrowLeft.add(arrowRight));
				}
			});

			_mfpOn(CHANGE_EVENT+ns, function() {
				if(mfp._preloadTimeout) clearTimeout(mfp._preloadTimeout);

				mfp._preloadTimeout = setTimeout(function() {
					mfp.preloadNearbyImages();
					mfp._preloadTimeout = null;
				}, 16);		
			});


			_mfpOn(CLOSE_EVENT+ns, function() {
				_document.off(ns);
				mfp.wrap.off('click'+ns);
			
				if(mfp.arrowLeft && supportsFastClick) {
					mfp.arrowLeft.add(mfp.arrowRight).destroyMfpFastClick();
				}
				mfp.arrowRight = mfp.arrowLeft = null;
			});

		}, 
		next: function() {
			mfp.direction = true;
			mfp.index = _getLoopedId(mfp.index + 1);
			mfp.updateItemHTML();
		},
		prev: function() {
			mfp.direction = false;
			mfp.index = _getLoopedId(mfp.index - 1);
			mfp.updateItemHTML();
		},
		goTo: function(newIndex) {
			mfp.direction = (newIndex >= mfp.index);
			mfp.index = newIndex;
			mfp.updateItemHTML();
		},
		preloadNearbyImages: function() {
			var p = mfp.st.gallery.preload,
				preloadBefore = Math.min(p[0], mfp.items.length),
				preloadAfter = Math.min(p[1], mfp.items.length),
				i;

			for(i = 1; i <= (mfp.direction ? preloadAfter : preloadBefore); i++) {
				mfp._preloadItem(mfp.index+i);
			}
			for(i = 1; i <= (mfp.direction ? preloadBefore : preloadAfter); i++) {
				mfp._preloadItem(mfp.index-i);
			}
		},
		_preloadItem: function(index) {
			index = _getLoopedId(index);

			if(mfp.items[index].preloaded) {
				return;
			}

			var item = mfp.items[index];
			if(!item.parsed) {
				item = mfp.parseEl( index );
			}

			_mfpTrigger('LazyLoad', item);

			if(item.type === 'image') {
				item.img = $('<img class="mfp-img" />').on('load.mfploader', function() {
					item.hasSize = true;
				}).on('error.mfploader', function() {
					item.hasSize = true;
					item.loadError = true;
					_mfpTrigger('LazyLoadError', item);
				}).attr('src', item.src);
			}


			item.preloaded = true;
		}
	}
});

/*
Touch Support that might be implemented some day

addSwipeGesture: function() {
	var startX,
		moved,
		multipleTouches;

		return;

	var namespace = '.mfp',
		addEventNames = function(pref, down, move, up, cancel) {
			mfp._tStart = pref + down + namespace;
			mfp._tMove = pref + move + namespace;
			mfp._tEnd = pref + up + namespace;
			mfp._tCancel = pref + cancel + namespace;
		};

	if(window.navigator.msPointerEnabled) {
		addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
	} else if('ontouchstart' in window) {
		addEventNames('touch', 'start', 'move', 'end', 'cancel');
	} else {
		return;
	}
	_window.on(mfp._tStart, function(e) {
		var oE = e.originalEvent;
		multipleTouches = moved = false;
		startX = oE.pageX || oE.changedTouches[0].pageX;
	}).on(mfp._tMove, function(e) {
		if(e.originalEvent.touches.length > 1) {
			multipleTouches = e.originalEvent.touches.length;
		} else {
			//e.preventDefault();
			moved = true;
		}
	}).on(mfp._tEnd + ' ' + mfp._tCancel, function(e) {
		if(moved && !multipleTouches) {
			var oE = e.originalEvent,
				diff = startX - (oE.pageX || oE.changedTouches[0].pageX);

			if(diff > 20) {
				mfp.next();
			} else if(diff < -20) {
				mfp.prev();
			}
		}
	});
},
*/


/*>>gallery*/

/*>>retina*/

var RETINA_NS = 'retina';

$.magnificPopup.registerModule(RETINA_NS, {
	options: {
		replaceSrc: function(item) {
			return item.src.replace(/\.\w+$/, function(m) { return '@2x' + m; });
		},
		ratio: 1 // Function or number.  Set to 1 to disable.
	},
	proto: {
		initRetina: function() {
			if(window.devicePixelRatio > 1) {

				var st = mfp.st.retina,
					ratio = st.ratio;

				ratio = !isNaN(ratio) ? ratio : ratio();

				if(ratio > 1) {
					_mfpOn('ImageHasSize' + '.' + RETINA_NS, function(e, item) {
						item.img.css({
							'max-width': item.img[0].naturalWidth / ratio,
							'width': '100%'
						});
					});
					_mfpOn('ElementParse' + '.' + RETINA_NS, function(e, item) {
						item.src = st.replaceSrc(item, ratio);
					});
				}
			}

		}
	}
});

/*>>retina*/

/*>>fastclick*/
/**
 * FastClick event implementation. (removes 300ms delay on touch devices)
 * Based on https://developers.google.com/mobile/articles/fast_buttons
 *
 * You may use it outside the Magnific Popup by calling just:
 *
 * $('.your-el').mfpFastClick(function() {
 *     console.log('Clicked!');
 * });
 *
 * To unbind:
 * $('.your-el').destroyMfpFastClick();
 * 
 * 
 * Note that it's a very basic and simple implementation, it blocks ghost click on the same element where it was bound.
 * If you need something more advanced, use plugin by FT Labs https://github.com/ftlabs/fastclick
 * 
 */

(function() {
	var ghostClickDelay = 1000,
		supportsTouch = 'ontouchstart' in window,
		unbindTouchMove = function() {
			_window.off('touchmove'+ns+' touchend'+ns);
		},
		eName = 'mfpFastClick',
		ns = '.'+eName;


	// As Zepto.js doesn't have an easy way to add custom events (like jQuery), so we implement it in this way
	$.fn.mfpFastClick = function(callback) {

		return $(this).each(function() {

			var elem = $(this),
				lock;

			if( supportsTouch ) {

				var timeout,
					startX,
					startY,
					pointerMoved,
					point,
					numPointers;

				elem.on('touchstart' + ns, function(e) {
					pointerMoved = false;
					numPointers = 1;

					point = e.originalEvent ? e.originalEvent.touches[0] : e.touches[0];
					startX = point.clientX;
					startY = point.clientY;

					_window.on('touchmove'+ns, function(e) {
						point = e.originalEvent ? e.originalEvent.touches : e.touches;
						numPointers = point.length;
						point = point[0];
						if (Math.abs(point.clientX - startX) > 10 ||
							Math.abs(point.clientY - startY) > 10) {
							pointerMoved = true;
							unbindTouchMove();
						}
					}).on('touchend'+ns, function(e) {
						unbindTouchMove();
						if(pointerMoved || numPointers > 1) {
							return;
						}
						lock = true;
						e.preventDefault();
						clearTimeout(timeout);
						timeout = setTimeout(function() {
							lock = false;
						}, ghostClickDelay);
						callback();
					});
				});

			}

			elem.on('click' + ns, function() {
				if(!lock) {
					callback();
				}
			});
		});
	};

	$.fn.destroyMfpFastClick = function() {
		$(this).off('touchstart' + ns + ' click' + ns);
		if(supportsTouch) _window.off('touchmove'+ns+' touchend'+ns);
	};
})();

/*>>fastclick*/
 _checkInstance(); }));
/* @preserve
 * Leaflet 1.8.0, a JS library for interactive maps. https://leafletjs.com
 * (c) 2010-2022 Vladimir Agafonkin, (c) 2010-2011 CloudMade
 */

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}));
})(this, (function (exports) { 'use strict';

  var version = "1.8.0";

  /*
   * @namespace Util
   *
   * Various utility functions, used by Leaflet internally.
   */

  // @function extend(dest: Object, src?: Object): Object
  // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
  function extend(dest) {
  	var i, j, len, src;

  	for (j = 1, len = arguments.length; j < len; j++) {
  		src = arguments[j];
  		for (i in src) {
  			dest[i] = src[i];
  		}
  	}
  	return dest;
  }

  // @function create(proto: Object, properties?: Object): Object
  // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
  var create$2 = Object.create || (function () {
  	function F() {}
  	return function (proto) {
  		F.prototype = proto;
  		return new F();
  	};
  })();

  // @function bind(fn: Function, …): Function
  // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
  // Has a `L.bind()` shortcut.
  function bind(fn, obj) {
  	var slice = Array.prototype.slice;

  	if (fn.bind) {
  		return fn.bind.apply(fn, slice.call(arguments, 1));
  	}

  	var args = slice.call(arguments, 2);

  	return function () {
  		return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
  	};
  }

  // @property lastId: Number
  // Last unique ID used by [`stamp()`](#util-stamp)
  var lastId = 0;

  // @function stamp(obj: Object): Number
  // Returns the unique ID of an object, assigning it one if it doesn't have it.
  function stamp(obj) {
  	if (!('_leaflet_id' in obj)) {
  		obj['_leaflet_id'] = ++lastId;
  	}
  	return obj._leaflet_id;
  }

  // @function throttle(fn: Function, time: Number, context: Object): Function
  // Returns a function which executes function `fn` with the given scope `context`
  // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
  // `fn` will be called no more than one time per given amount of `time`. The arguments
  // received by the bound function will be any arguments passed when binding the
  // function, followed by any arguments passed when invoking the bound function.
  // Has an `L.throttle` shortcut.
  function throttle(fn, time, context) {
  	var lock, args, wrapperFn, later;

  	later = function () {
  		// reset lock and call if queued
  		lock = false;
  		if (args) {
  			wrapperFn.apply(context, args);
  			args = false;
  		}
  	};

  	wrapperFn = function () {
  		if (lock) {
  			// called too soon, queue to call later
  			args = arguments;

  		} else {
  			// call and lock until later
  			fn.apply(context, arguments);
  			setTimeout(later, time);
  			lock = true;
  		}
  	};

  	return wrapperFn;
  }

  // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
  // Returns the number `num` modulo `range` in such a way so it lies within
  // `range[0]` and `range[1]`. The returned value will be always smaller than
  // `range[1]` unless `includeMax` is set to `true`.
  function wrapNum(x, range, includeMax) {
  	var max = range[1],
  	    min = range[0],
  	    d = max - min;
  	return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
  }

  // @function falseFn(): Function
  // Returns a function which always returns `false`.
  function falseFn() { return false; }

  // @function formatNum(num: Number, precision?: Number|false): Number
  // Returns the number `num` rounded with specified `precision`.
  // The default `precision` value is 6 decimal places.
  // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
  function formatNum(num, precision) {
  	if (precision === false) { return num; }
  	var pow = Math.pow(10, precision === undefined ? 6 : precision);
  	return Math.round(num * pow) / pow;
  }

  // @function trim(str: String): String
  // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
  function trim(str) {
  	return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
  }

  // @function splitWords(str: String): String[]
  // Trims and splits the string on whitespace and returns the array of parts.
  function splitWords(str) {
  	return trim(str).split(/\s+/);
  }

  // @function setOptions(obj: Object, options: Object): Object
  // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
  function setOptions(obj, options) {
  	if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
  		obj.options = obj.options ? create$2(obj.options) : {};
  	}
  	for (var i in options) {
  		obj.options[i] = options[i];
  	}
  	return obj.options;
  }

  // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
  // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
  // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
  // be appended at the end. If `uppercase` is `true`, the parameter names will
  // be uppercased (e.g. `'?A=foo&B=bar'`)
  function getParamString(obj, existingUrl, uppercase) {
  	var params = [];
  	for (var i in obj) {
  		params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
  	}
  	return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
  }

  var templateRe = /\{ *([\w_ -]+) *\}/g;

  // @function template(str: String, data: Object): String
  // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
  // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
  // `('Hello foo, bar')`. You can also specify functions instead of strings for
  // data values — they will be evaluated passing `data` as an argument.
  function template(str, data) {
  	return str.replace(templateRe, function (str, key) {
  		var value = data[key];

  		if (value === undefined) {
  			throw new Error('No value provided for variable ' + str);

  		} else if (typeof value === 'function') {
  			value = value(data);
  		}
  		return value;
  	});
  }

  // @function isArray(obj): Boolean
  // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
  var isArray = Array.isArray || function (obj) {
  	return (Object.prototype.toString.call(obj) === '[object Array]');
  };

  // @function indexOf(array: Array, el: Object): Number
  // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
  function indexOf(array, el) {
  	for (var i = 0; i < array.length; i++) {
  		if (array[i] === el) { return i; }
  	}
  	return -1;
  }

  // @property emptyImageUrl: String
  // Data URI string containing a base64-encoded empty GIF image.
  // Used as a hack to free memory from unused images on WebKit-powered
  // mobile devices (by setting image `src` to this string).
  var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';

  // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/

  function getPrefixed(name) {
  	return window['webkit' + name] || window['moz' + name] || window['ms' + name];
  }

  var lastTime = 0;

  // fallback for IE 7-8
  function timeoutDefer(fn) {
  	var time = +new Date(),
  	    timeToCall = Math.max(0, 16 - (time - lastTime));

  	lastTime = time + timeToCall;
  	return window.setTimeout(fn, timeToCall);
  }

  var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
  var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
  		getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };

  // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
  // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
  // `context` if given. When `immediate` is set, `fn` is called immediately if
  // the browser doesn't have native support for
  // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
  // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
  function requestAnimFrame(fn, context, immediate) {
  	if (immediate && requestFn === timeoutDefer) {
  		fn.call(context);
  	} else {
  		return requestFn.call(window, bind(fn, context));
  	}
  }

  // @function cancelAnimFrame(id: Number): undefined
  // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
  function cancelAnimFrame(id) {
  	if (id) {
  		cancelFn.call(window, id);
  	}
  }

  var Util = {
    __proto__: null,
    extend: extend,
    create: create$2,
    bind: bind,
    get lastId () { return lastId; },
    stamp: stamp,
    throttle: throttle,
    wrapNum: wrapNum,
    falseFn: falseFn,
    formatNum: formatNum,
    trim: trim,
    splitWords: splitWords,
    setOptions: setOptions,
    getParamString: getParamString,
    template: template,
    isArray: isArray,
    indexOf: indexOf,
    emptyImageUrl: emptyImageUrl,
    requestFn: requestFn,
    cancelFn: cancelFn,
    requestAnimFrame: requestAnimFrame,
    cancelAnimFrame: cancelAnimFrame
  };

  // @class Class
  // @aka L.Class

  // @section
  // @uninheritable

  // Thanks to John Resig and Dean Edwards for inspiration!

  function Class() {}

  Class.extend = function (props) {

  	// @function extend(props: Object): Function
  	// [Extends the current class](#class-inheritance) given the properties to be included.
  	// Returns a Javascript function that is a class constructor (to be called with `new`).
  	var NewClass = function () {

  		setOptions(this);

  		// call the constructor
  		if (this.initialize) {
  			this.initialize.apply(this, arguments);
  		}

  		// call all constructor hooks
  		this.callInitHooks();
  	};

  	var parentProto = NewClass.__super__ = this.prototype;

  	var proto = create$2(parentProto);
  	proto.constructor = NewClass;

  	NewClass.prototype = proto;

  	// inherit parent's statics
  	for (var i in this) {
  		if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
  			NewClass[i] = this[i];
  		}
  	}

  	// mix static properties into the class
  	if (props.statics) {
  		extend(NewClass, props.statics);
  	}

  	// mix includes into the prototype
  	if (props.includes) {
  		checkDeprecatedMixinEvents(props.includes);
  		extend.apply(null, [proto].concat(props.includes));
  	}

  	// mix given properties into the prototype
  	extend(proto, props);
  	delete proto.statics;
  	delete proto.includes;

  	// merge options
  	if (proto.options) {
  		proto.options = parentProto.options ? create$2(parentProto.options) : {};
  		extend(proto.options, props.options);
  	}

  	proto._initHooks = [];

  	// add method for calling all hooks
  	proto.callInitHooks = function () {

  		if (this._initHooksCalled) { return; }

  		if (parentProto.callInitHooks) {
  			parentProto.callInitHooks.call(this);
  		}

  		this._initHooksCalled = true;

  		for (var i = 0, len = proto._initHooks.length; i < len; i++) {
  			proto._initHooks[i].call(this);
  		}
  	};

  	return NewClass;
  };


  // @function include(properties: Object): this
  // [Includes a mixin](#class-includes) into the current class.
  Class.include = function (props) {
  	var parentOptions = this.prototype.options;
  	extend(this.prototype, props);
  	if (props.options) {
  		this.prototype.options = parentOptions;
  		this.mergeOptions(props.options);
  	}
  	return this;
  };

  // @function mergeOptions(options: Object): this
  // [Merges `options`](#class-options) into the defaults of the class.
  Class.mergeOptions = function (options) {
  	extend(this.prototype.options, options);
  	return this;
  };

  // @function addInitHook(fn: Function): this
  // Adds a [constructor hook](#class-constructor-hooks) to the class.
  Class.addInitHook = function (fn) { // (Function) || (String, args...)
  	var args = Array.prototype.slice.call(arguments, 1);

  	var init = typeof fn === 'function' ? fn : function () {
  		this[fn].apply(this, args);
  	};

  	this.prototype._initHooks = this.prototype._initHooks || [];
  	this.prototype._initHooks.push(init);
  	return this;
  };

  function checkDeprecatedMixinEvents(includes) {
  	if (typeof L === 'undefined' || !L || !L.Mixin) { return; }

  	includes = isArray(includes) ? includes : [includes];

  	for (var i = 0; i < includes.length; i++) {
  		if (includes[i] === L.Mixin.Events) {
  			console.warn('Deprecated include of L.Mixin.Events: ' +
  				'this property will be removed in future releases, ' +
  				'please inherit from L.Evented instead.', new Error().stack);
  		}
  	}
  }

  /*
   * @class Evented
   * @aka L.Evented
   * @inherits Class
   *
   * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
   *
   * @example
   *
   * ```js
   * map.on('click', function(e) {
   * 	alert(e.latlng);
   * } );
   * ```
   *
   * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
   *
   * ```js
   * function onClick(e) { ... }
   *
   * map.on('click', onClick);
   * map.off('click', onClick);
   * ```
   */

  var Events = {
  	/* @method on(type: String, fn: Function, context?: Object): this
  	 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
  	 *
  	 * @alternative
  	 * @method on(eventMap: Object): this
  	 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
  	 */
  	on: function (types, fn, context) {

  		// types can be a map of types/handlers
  		if (typeof types === 'object') {
  			for (var type in types) {
  				// we don't process space-separated events here for performance;
  				// it's a hot path since Layer uses the on(obj) syntax
  				this._on(type, types[type], fn);
  			}

  		} else {
  			// types can be a string of space-separated words
  			types = splitWords(types);

  			for (var i = 0, len = types.length; i < len; i++) {
  				this._on(types[i], fn, context);
  			}
  		}

  		return this;
  	},

  	/* @method off(type: String, fn?: Function, context?: Object): this
  	 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
  	 *
  	 * @alternative
  	 * @method off(eventMap: Object): this
  	 * Removes a set of type/listener pairs.
  	 *
  	 * @alternative
  	 * @method off: this
  	 * Removes all listeners to all events on the object. This includes implicitly attached events.
  	 */
  	off: function (types, fn, context) {

  		if (!arguments.length) {
  			// clear all listeners if called without arguments
  			delete this._events;

  		} else if (typeof types === 'object') {
  			for (var type in types) {
  				this._off(type, types[type], fn);
  			}

  		} else {
  			types = splitWords(types);

  			var removeAll = arguments.length === 1;
  			for (var i = 0, len = types.length; i < len; i++) {
  				if (removeAll) {
  					this._off(types[i]);
  				} else {
  					this._off(types[i], fn, context);
  				}
  			}
  		}

  		return this;
  	},

  	// attach listener (without syntactic sugar now)
  	_on: function (type, fn, context) {
  		if (typeof fn !== 'function') {
  			console.warn('wrong listener type: ' + typeof fn);
  			return;
  		}
  		this._events = this._events || {};

  		/* get/init listeners for type */
  		var typeListeners = this._events[type];
  		if (!typeListeners) {
  			typeListeners = [];
  			this._events[type] = typeListeners;
  		}

  		if (context === this) {
  			// Less memory footprint.
  			context = undefined;
  		}
  		var newListener = {fn: fn, ctx: context},
  		    listeners = typeListeners;

  		// check if fn already there
  		for (var i = 0, len = listeners.length; i < len; i++) {
  			if (listeners[i].fn === fn && listeners[i].ctx === context) {
  				return;
  			}
  		}

  		listeners.push(newListener);
  	},

  	_off: function (type, fn, context) {
  		var listeners,
  		    i,
  		    len;

  		if (!this._events) { return; }

  		listeners = this._events[type];

  		if (!listeners) {
  			return;
  		}

  		if (arguments.length === 1) { // remove all
  			if (this._firingCount) {
  				// Set all removed listeners to noop
  				// so they are not called if remove happens in fire
  				for (i = 0, len = listeners.length; i < len; i++) {
  					listeners[i].fn = falseFn;
  				}
  			}
  			// clear all listeners for a type if function isn't specified
  			delete this._events[type];
  			return;
  		}

  		if (context === this) {
  			context = undefined;
  		}

  		if (typeof fn !== 'function') {
  			console.warn('wrong listener type: ' + typeof fn);
  			return;
  		}
  		// find fn and remove it
  		for (i = 0, len = listeners.length; i < len; i++) {
  			var l = listeners[i];
  			if (l.ctx !== context) { continue; }
  			if (l.fn === fn) {
  				if (this._firingCount) {
  					// set the removed listener to noop so that's not called if remove happens in fire
  					l.fn = falseFn;

  					/* copy array in case events are being fired */
  					this._events[type] = listeners = listeners.slice();
  				}
  				listeners.splice(i, 1);

  				return;
  			}
  		}
  		console.warn('listener not found');
  	},

  	// @method fire(type: String, data?: Object, propagate?: Boolean): this
  	// Fires an event of the specified type. You can optionally provide a data
  	// object — the first argument of the listener function will contain its
  	// properties. The event can optionally be propagated to event parents.
  	fire: function (type, data, propagate) {
  		if (!this.listens(type, propagate)) { return this; }

  		var event = extend({}, data, {
  			type: type,
  			target: this,
  			sourceTarget: data && data.sourceTarget || this
  		});

  		if (this._events) {
  			var listeners = this._events[type];

  			if (listeners) {
  				this._firingCount = (this._firingCount + 1) || 1;
  				for (var i = 0, len = listeners.length; i < len; i++) {
  					var l = listeners[i];
  					l.fn.call(l.ctx || this, event);
  				}

  				this._firingCount--;
  			}
  		}

  		if (propagate) {
  			// propagate the event to parents (set with addEventParent)
  			this._propagateEvent(event);
  		}

  		return this;
  	},

  	// @method listens(type: String, propagate?: Boolean): Boolean
  	// Returns `true` if a particular event type has any listeners attached to it.
  	// The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
  	listens: function (type, propagate) {
  		if (typeof type !== 'string') {
  			console.warn('"string" type argument expected');
  		}
  		var listeners = this._events && this._events[type];
  		if (listeners && listeners.length) { return true; }

  		if (propagate) {
  			// also check parents for listeners if event propagates
  			for (var id in this._eventParents) {
  				if (this._eventParents[id].listens(type, propagate)) { return true; }
  			}
  		}
  		return false;
  	},

  	// @method once(…): this
  	// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
  	once: function (types, fn, context) {

  		if (typeof types === 'object') {
  			for (var type in types) {
  				this.once(type, types[type], fn);
  			}
  			return this;
  		}

  		var handler = bind(function () {
  			this
  			    .off(types, fn, context)
  			    .off(types, handler, context);
  		}, this);

  		// add a listener that's executed once and removed after that
  		return this
  		    .on(types, fn, context)
  		    .on(types, handler, context);
  	},

  	// @method addEventParent(obj: Evented): this
  	// Adds an event parent - an `Evented` that will receive propagated events
  	addEventParent: function (obj) {
  		this._eventParents = this._eventParents || {};
  		this._eventParents[stamp(obj)] = obj;
  		return this;
  	},

  	// @method removeEventParent(obj: Evented): this
  	// Removes an event parent, so it will stop receiving propagated events
  	removeEventParent: function (obj) {
  		if (this._eventParents) {
  			delete this._eventParents[stamp(obj)];
  		}
  		return this;
  	},

  	_propagateEvent: function (e) {
  		for (var id in this._eventParents) {
  			this._eventParents[id].fire(e.type, extend({
  				layer: e.target,
  				propagatedFrom: e.target
  			}, e), true);
  		}
  	}
  };

  // aliases; we should ditch those eventually

  // @method addEventListener(…): this
  // Alias to [`on(…)`](#evented-on)
  Events.addEventListener = Events.on;

  // @method removeEventListener(…): this
  // Alias to [`off(…)`](#evented-off)

  // @method clearAllEventListeners(…): this
  // Alias to [`off()`](#evented-off)
  Events.removeEventListener = Events.clearAllEventListeners = Events.off;

  // @method addOneTimeEventListener(…): this
  // Alias to [`once(…)`](#evented-once)
  Events.addOneTimeEventListener = Events.once;

  // @method fireEvent(…): this
  // Alias to [`fire(…)`](#evented-fire)
  Events.fireEvent = Events.fire;

  // @method hasEventListeners(…): Boolean
  // Alias to [`listens(…)`](#evented-listens)
  Events.hasEventListeners = Events.listens;

  var Evented = Class.extend(Events);

  /*
   * @class Point
   * @aka L.Point
   *
   * Represents a point with `x` and `y` coordinates in pixels.
   *
   * @example
   *
   * ```js
   * var point = L.point(200, 300);
   * ```
   *
   * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
   *
   * ```js
   * map.panBy([200, 300]);
   * map.panBy(L.point(200, 300));
   * ```
   *
   * Note that `Point` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function Point(x, y, round) {
  	// @property x: Number; The `x` coordinate of the point
  	this.x = (round ? Math.round(x) : x);
  	// @property y: Number; The `y` coordinate of the point
  	this.y = (round ? Math.round(y) : y);
  }

  var trunc = Math.trunc || function (v) {
  	return v > 0 ? Math.floor(v) : Math.ceil(v);
  };

  Point.prototype = {

  	// @method clone(): Point
  	// Returns a copy of the current point.
  	clone: function () {
  		return new Point(this.x, this.y);
  	},

  	// @method add(otherPoint: Point): Point
  	// Returns the result of addition of the current and the given points.
  	add: function (point) {
  		// non-destructive, returns a new point
  		return this.clone()._add(toPoint(point));
  	},

  	_add: function (point) {
  		// destructive, used directly for performance in situations where it's safe to modify existing point
  		this.x += point.x;
  		this.y += point.y;
  		return this;
  	},

  	// @method subtract(otherPoint: Point): Point
  	// Returns the result of subtraction of the given point from the current.
  	subtract: function (point) {
  		return this.clone()._subtract(toPoint(point));
  	},

  	_subtract: function (point) {
  		this.x -= point.x;
  		this.y -= point.y;
  		return this;
  	},

  	// @method divideBy(num: Number): Point
  	// Returns the result of division of the current point by the given number.
  	divideBy: function (num) {
  		return this.clone()._divideBy(num);
  	},

  	_divideBy: function (num) {
  		this.x /= num;
  		this.y /= num;
  		return this;
  	},

  	// @method multiplyBy(num: Number): Point
  	// Returns the result of multiplication of the current point by the given number.
  	multiplyBy: function (num) {
  		return this.clone()._multiplyBy(num);
  	},

  	_multiplyBy: function (num) {
  		this.x *= num;
  		this.y *= num;
  		return this;
  	},

  	// @method scaleBy(scale: Point): Point
  	// Multiply each coordinate of the current point by each coordinate of
  	// `scale`. In linear algebra terms, multiply the point by the
  	// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
  	// defined by `scale`.
  	scaleBy: function (point) {
  		return new Point(this.x * point.x, this.y * point.y);
  	},

  	// @method unscaleBy(scale: Point): Point
  	// Inverse of `scaleBy`. Divide each coordinate of the current point by
  	// each coordinate of `scale`.
  	unscaleBy: function (point) {
  		return new Point(this.x / point.x, this.y / point.y);
  	},

  	// @method round(): Point
  	// Returns a copy of the current point with rounded coordinates.
  	round: function () {
  		return this.clone()._round();
  	},

  	_round: function () {
  		this.x = Math.round(this.x);
  		this.y = Math.round(this.y);
  		return this;
  	},

  	// @method floor(): Point
  	// Returns a copy of the current point with floored coordinates (rounded down).
  	floor: function () {
  		return this.clone()._floor();
  	},

  	_floor: function () {
  		this.x = Math.floor(this.x);
  		this.y = Math.floor(this.y);
  		return this;
  	},

  	// @method ceil(): Point
  	// Returns a copy of the current point with ceiled coordinates (rounded up).
  	ceil: function () {
  		return this.clone()._ceil();
  	},

  	_ceil: function () {
  		this.x = Math.ceil(this.x);
  		this.y = Math.ceil(this.y);
  		return this;
  	},

  	// @method trunc(): Point
  	// Returns a copy of the current point with truncated coordinates (rounded towards zero).
  	trunc: function () {
  		return this.clone()._trunc();
  	},

  	_trunc: function () {
  		this.x = trunc(this.x);
  		this.y = trunc(this.y);
  		return this;
  	},

  	// @method distanceTo(otherPoint: Point): Number
  	// Returns the cartesian distance between the current and the given points.
  	distanceTo: function (point) {
  		point = toPoint(point);

  		var x = point.x - this.x,
  		    y = point.y - this.y;

  		return Math.sqrt(x * x + y * y);
  	},

  	// @method equals(otherPoint: Point): Boolean
  	// Returns `true` if the given point has the same coordinates.
  	equals: function (point) {
  		point = toPoint(point);

  		return point.x === this.x &&
  		       point.y === this.y;
  	},

  	// @method contains(otherPoint: Point): Boolean
  	// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
  	contains: function (point) {
  		point = toPoint(point);

  		return Math.abs(point.x) <= Math.abs(this.x) &&
  		       Math.abs(point.y) <= Math.abs(this.y);
  	},

  	// @method toString(): String
  	// Returns a string representation of the point for debugging purposes.
  	toString: function () {
  		return 'Point(' +
  		        formatNum(this.x) + ', ' +
  		        formatNum(this.y) + ')';
  	}
  };

  // @factory L.point(x: Number, y: Number, round?: Boolean)
  // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.

  // @alternative
  // @factory L.point(coords: Number[])
  // Expects an array of the form `[x, y]` instead.

  // @alternative
  // @factory L.point(coords: Object)
  // Expects a plain object of the form `{x: Number, y: Number}` instead.
  function toPoint(x, y, round) {
  	if (x instanceof Point) {
  		return x;
  	}
  	if (isArray(x)) {
  		return new Point(x[0], x[1]);
  	}
  	if (x === undefined || x === null) {
  		return x;
  	}
  	if (typeof x === 'object' && 'x' in x && 'y' in x) {
  		return new Point(x.x, x.y);
  	}
  	return new Point(x, y, round);
  }

  /*
   * @class Bounds
   * @aka L.Bounds
   *
   * Represents a rectangular area in pixel coordinates.
   *
   * @example
   *
   * ```js
   * var p1 = L.point(10, 10),
   * p2 = L.point(40, 60),
   * bounds = L.bounds(p1, p2);
   * ```
   *
   * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
   *
   * ```js
   * otherBounds.intersects([[10, 10], [40, 60]]);
   * ```
   *
   * Note that `Bounds` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function Bounds(a, b) {
  	if (!a) { return; }

  	var points = b ? [a, b] : a;

  	for (var i = 0, len = points.length; i < len; i++) {
  		this.extend(points[i]);
  	}
  }

  Bounds.prototype = {
  	// @method extend(point: Point): this
  	// Extends the bounds to contain the given point.
  	extend: function (point) { // (Point)
  		point = toPoint(point);

  		// @property min: Point
  		// The top left corner of the rectangle.
  		// @property max: Point
  		// The bottom right corner of the rectangle.
  		if (!this.min && !this.max) {
  			this.min = point.clone();
  			this.max = point.clone();
  		} else {
  			this.min.x = Math.min(point.x, this.min.x);
  			this.max.x = Math.max(point.x, this.max.x);
  			this.min.y = Math.min(point.y, this.min.y);
  			this.max.y = Math.max(point.y, this.max.y);
  		}
  		return this;
  	},

  	// @method getCenter(round?: Boolean): Point
  	// Returns the center point of the bounds.
  	getCenter: function (round) {
  		return new Point(
  		        (this.min.x + this.max.x) / 2,
  		        (this.min.y + this.max.y) / 2, round);
  	},

  	// @method getBottomLeft(): Point
  	// Returns the bottom-left point of the bounds.
  	getBottomLeft: function () {
  		return new Point(this.min.x, this.max.y);
  	},

  	// @method getTopRight(): Point
  	// Returns the top-right point of the bounds.
  	getTopRight: function () { // -> Point
  		return new Point(this.max.x, this.min.y);
  	},

  	// @method getTopLeft(): Point
  	// Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
  	getTopLeft: function () {
  		return this.min; // left, top
  	},

  	// @method getBottomRight(): Point
  	// Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
  	getBottomRight: function () {
  		return this.max; // right, bottom
  	},

  	// @method getSize(): Point
  	// Returns the size of the given bounds
  	getSize: function () {
  		return this.max.subtract(this.min);
  	},

  	// @method contains(otherBounds: Bounds): Boolean
  	// Returns `true` if the rectangle contains the given one.
  	// @alternative
  	// @method contains(point: Point): Boolean
  	// Returns `true` if the rectangle contains the given point.
  	contains: function (obj) {
  		var min, max;

  		if (typeof obj[0] === 'number' || obj instanceof Point) {
  			obj = toPoint(obj);
  		} else {
  			obj = toBounds(obj);
  		}

  		if (obj instanceof Bounds) {
  			min = obj.min;
  			max = obj.max;
  		} else {
  			min = max = obj;
  		}

  		return (min.x >= this.min.x) &&
  		       (max.x <= this.max.x) &&
  		       (min.y >= this.min.y) &&
  		       (max.y <= this.max.y);
  	},

  	// @method intersects(otherBounds: Bounds): Boolean
  	// Returns `true` if the rectangle intersects the given bounds. Two bounds
  	// intersect if they have at least one point in common.
  	intersects: function (bounds) { // (Bounds) -> Boolean
  		bounds = toBounds(bounds);

  		var min = this.min,
  		    max = this.max,
  		    min2 = bounds.min,
  		    max2 = bounds.max,
  		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
  		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);

  		return xIntersects && yIntersects;
  	},

  	// @method overlaps(otherBounds: Bounds): Boolean
  	// Returns `true` if the rectangle overlaps the given bounds. Two bounds
  	// overlap if their intersection is an area.
  	overlaps: function (bounds) { // (Bounds) -> Boolean
  		bounds = toBounds(bounds);

  		var min = this.min,
  		    max = this.max,
  		    min2 = bounds.min,
  		    max2 = bounds.max,
  		    xOverlaps = (max2.x > min.x) && (min2.x < max.x),
  		    yOverlaps = (max2.y > min.y) && (min2.y < max.y);

  		return xOverlaps && yOverlaps;
  	},

  	isValid: function () {
  		return !!(this.min && this.max);
  	}
  };


  // @factory L.bounds(corner1: Point, corner2: Point)
  // Creates a Bounds object from two corners coordinate pairs.
  // @alternative
  // @factory L.bounds(points: Point[])
  // Creates a Bounds object from the given array of points.
  function toBounds(a, b) {
  	if (!a || a instanceof Bounds) {
  		return a;
  	}
  	return new Bounds(a, b);
  }

  /*
   * @class LatLngBounds
   * @aka L.LatLngBounds
   *
   * Represents a rectangular geographical area on a map.
   *
   * @example
   *
   * ```js
   * var corner1 = L.latLng(40.712, -74.227),
   * corner2 = L.latLng(40.774, -74.125),
   * bounds = L.latLngBounds(corner1, corner2);
   * ```
   *
   * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
   *
   * ```js
   * map.fitBounds([
   * 	[40.712, -74.227],
   * 	[40.774, -74.125]
   * ]);
   * ```
   *
   * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
   *
   * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
  	if (!corner1) { return; }

  	var latlngs = corner2 ? [corner1, corner2] : corner1;

  	for (var i = 0, len = latlngs.length; i < len; i++) {
  		this.extend(latlngs[i]);
  	}
  }

  LatLngBounds.prototype = {

  	// @method extend(latlng: LatLng): this
  	// Extend the bounds to contain the given point

  	// @alternative
  	// @method extend(otherBounds: LatLngBounds): this
  	// Extend the bounds to contain the given bounds
  	extend: function (obj) {
  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2, ne2;

  		if (obj instanceof LatLng) {
  			sw2 = obj;
  			ne2 = obj;

  		} else if (obj instanceof LatLngBounds) {
  			sw2 = obj._southWest;
  			ne2 = obj._northEast;

  			if (!sw2 || !ne2) { return this; }

  		} else {
  			return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
  		}

  		if (!sw && !ne) {
  			this._southWest = new LatLng(sw2.lat, sw2.lng);
  			this._northEast = new LatLng(ne2.lat, ne2.lng);
  		} else {
  			sw.lat = Math.min(sw2.lat, sw.lat);
  			sw.lng = Math.min(sw2.lng, sw.lng);
  			ne.lat = Math.max(ne2.lat, ne.lat);
  			ne.lng = Math.max(ne2.lng, ne.lng);
  		}

  		return this;
  	},

  	// @method pad(bufferRatio: Number): LatLngBounds
  	// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
  	// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
  	// Negative values will retract the bounds.
  	pad: function (bufferRatio) {
  		var sw = this._southWest,
  		    ne = this._northEast,
  		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
  		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;

  		return new LatLngBounds(
  		        new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
  		        new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
  	},

  	// @method getCenter(): LatLng
  	// Returns the center point of the bounds.
  	getCenter: function () {
  		return new LatLng(
  		        (this._southWest.lat + this._northEast.lat) / 2,
  		        (this._southWest.lng + this._northEast.lng) / 2);
  	},

  	// @method getSouthWest(): LatLng
  	// Returns the south-west point of the bounds.
  	getSouthWest: function () {
  		return this._southWest;
  	},

  	// @method getNorthEast(): LatLng
  	// Returns the north-east point of the bounds.
  	getNorthEast: function () {
  		return this._northEast;
  	},

  	// @method getNorthWest(): LatLng
  	// Returns the north-west point of the bounds.
  	getNorthWest: function () {
  		return new LatLng(this.getNorth(), this.getWest());
  	},

  	// @method getSouthEast(): LatLng
  	// Returns the south-east point of the bounds.
  	getSouthEast: function () {
  		return new LatLng(this.getSouth(), this.getEast());
  	},

  	// @method getWest(): Number
  	// Returns the west longitude of the bounds
  	getWest: function () {
  		return this._southWest.lng;
  	},

  	// @method getSouth(): Number
  	// Returns the south latitude of the bounds
  	getSouth: function () {
  		return this._southWest.lat;
  	},

  	// @method getEast(): Number
  	// Returns the east longitude of the bounds
  	getEast: function () {
  		return this._northEast.lng;
  	},

  	// @method getNorth(): Number
  	// Returns the north latitude of the bounds
  	getNorth: function () {
  		return this._northEast.lat;
  	},

  	// @method contains(otherBounds: LatLngBounds): Boolean
  	// Returns `true` if the rectangle contains the given one.

  	// @alternative
  	// @method contains (latlng: LatLng): Boolean
  	// Returns `true` if the rectangle contains the given point.
  	contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
  		if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
  			obj = toLatLng(obj);
  		} else {
  			obj = toLatLngBounds(obj);
  		}

  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2, ne2;

  		if (obj instanceof LatLngBounds) {
  			sw2 = obj.getSouthWest();
  			ne2 = obj.getNorthEast();
  		} else {
  			sw2 = ne2 = obj;
  		}

  		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
  		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
  	},

  	// @method intersects(otherBounds: LatLngBounds): Boolean
  	// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
  	intersects: function (bounds) {
  		bounds = toLatLngBounds(bounds);

  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2 = bounds.getSouthWest(),
  		    ne2 = bounds.getNorthEast(),

  		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
  		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);

  		return latIntersects && lngIntersects;
  	},

  	// @method overlaps(otherBounds: LatLngBounds): Boolean
  	// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
  	overlaps: function (bounds) {
  		bounds = toLatLngBounds(bounds);

  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2 = bounds.getSouthWest(),
  		    ne2 = bounds.getNorthEast(),

  		    latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
  		    lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);

  		return latOverlaps && lngOverlaps;
  	},

  	// @method toBBoxString(): String
  	// Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
  	toBBoxString: function () {
  		return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
  	},

  	// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
  	// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
  	equals: function (bounds, maxMargin) {
  		if (!bounds) { return false; }

  		bounds = toLatLngBounds(bounds);

  		return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
  		       this._northEast.equals(bounds.getNorthEast(), maxMargin);
  	},

  	// @method isValid(): Boolean
  	// Returns `true` if the bounds are properly initialized.
  	isValid: function () {
  		return !!(this._southWest && this._northEast);
  	}
  };

  // TODO International date line?

  // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
  // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.

  // @alternative
  // @factory L.latLngBounds(latlngs: LatLng[])
  // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
  function toLatLngBounds(a, b) {
  	if (a instanceof LatLngBounds) {
  		return a;
  	}
  	return new LatLngBounds(a, b);
  }

  /* @class LatLng
   * @aka L.LatLng
   *
   * Represents a geographical point with a certain latitude and longitude.
   *
   * @example
   *
   * ```
   * var latlng = L.latLng(50.5, 30.5);
   * ```
   *
   * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
   *
   * ```
   * map.panTo([50, 30]);
   * map.panTo({lon: 30, lat: 50});
   * map.panTo({lat: 50, lng: 30});
   * map.panTo(L.latLng(50, 30));
   * ```
   *
   * Note that `LatLng` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function LatLng(lat, lng, alt) {
  	if (isNaN(lat) || isNaN(lng)) {
  		throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
  	}

  	// @property lat: Number
  	// Latitude in degrees
  	this.lat = +lat;

  	// @property lng: Number
  	// Longitude in degrees
  	this.lng = +lng;

  	// @property alt: Number
  	// Altitude in meters (optional)
  	if (alt !== undefined) {
  		this.alt = +alt;
  	}
  }

  LatLng.prototype = {
  	// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
  	// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
  	equals: function (obj, maxMargin) {
  		if (!obj) { return false; }

  		obj = toLatLng(obj);

  		var margin = Math.max(
  		        Math.abs(this.lat - obj.lat),
  		        Math.abs(this.lng - obj.lng));

  		return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
  	},

  	// @method toString(): String
  	// Returns a string representation of the point (for debugging purposes).
  	toString: function (precision) {
  		return 'LatLng(' +
  		        formatNum(this.lat, precision) + ', ' +
  		        formatNum(this.lng, precision) + ')';
  	},

  	// @method distanceTo(otherLatLng: LatLng): Number
  	// Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
  	distanceTo: function (other) {
  		return Earth.distance(this, toLatLng(other));
  	},

  	// @method wrap(): LatLng
  	// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
  	wrap: function () {
  		return Earth.wrapLatLng(this);
  	},

  	// @method toBounds(sizeInMeters: Number): LatLngBounds
  	// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
  	toBounds: function (sizeInMeters) {
  		var latAccuracy = 180 * sizeInMeters / 40075017,
  		    lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);

  		return toLatLngBounds(
  		        [this.lat - latAccuracy, this.lng - lngAccuracy],
  		        [this.lat + latAccuracy, this.lng + lngAccuracy]);
  	},

  	clone: function () {
  		return new LatLng(this.lat, this.lng, this.alt);
  	}
  };



  // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
  // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).

  // @alternative
  // @factory L.latLng(coords: Array): LatLng
  // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.

  // @alternative
  // @factory L.latLng(coords: Object): LatLng
  // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.

  function toLatLng(a, b, c) {
  	if (a instanceof LatLng) {
  		return a;
  	}
  	if (isArray(a) && typeof a[0] !== 'object') {
  		if (a.length === 3) {
  			return new LatLng(a[0], a[1], a[2]);
  		}
  		if (a.length === 2) {
  			return new LatLng(a[0], a[1]);
  		}
  		return null;
  	}
  	if (a === undefined || a === null) {
  		return a;
  	}
  	if (typeof a === 'object' && 'lat' in a) {
  		return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
  	}
  	if (b === undefined) {
  		return null;
  	}
  	return new LatLng(a, b, c);
  }

  /*
   * @namespace CRS
   * @crs L.CRS.Base
   * Object that defines coordinate reference systems for projecting
   * geographical points into pixel (screen) coordinates and back (and to
   * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
   * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
   *
   * Leaflet defines the most usual CRSs by default. If you want to use a
   * CRS not defined by default, take a look at the
   * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
   *
   * Note that the CRS instances do not inherit from Leaflet's `Class` object,
   * and can't be instantiated. Also, new classes can't inherit from them,
   * and methods can't be added to them with the `include` function.
   */

  var CRS = {
  	// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
  	// Projects geographical coordinates into pixel coordinates for a given zoom.
  	latLngToPoint: function (latlng, zoom) {
  		var projectedPoint = this.projection.project(latlng),
  		    scale = this.scale(zoom);

  		return this.transformation._transform(projectedPoint, scale);
  	},

  	// @method pointToLatLng(point: Point, zoom: Number): LatLng
  	// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
  	// zoom into geographical coordinates.
  	pointToLatLng: function (point, zoom) {
  		var scale = this.scale(zoom),
  		    untransformedPoint = this.transformation.untransform(point, scale);

  		return this.projection.unproject(untransformedPoint);
  	},

  	// @method project(latlng: LatLng): Point
  	// Projects geographical coordinates into coordinates in units accepted for
  	// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
  	project: function (latlng) {
  		return this.projection.project(latlng);
  	},

  	// @method unproject(point: Point): LatLng
  	// Given a projected coordinate returns the corresponding LatLng.
  	// The inverse of `project`.
  	unproject: function (point) {
  		return this.projection.unproject(point);
  	},

  	// @method scale(zoom: Number): Number
  	// Returns the scale used when transforming projected coordinates into
  	// pixel coordinates for a particular zoom. For example, it returns
  	// `256 * 2^zoom` for Mercator-based CRS.
  	scale: function (zoom) {
  		return 256 * Math.pow(2, zoom);
  	},

  	// @method zoom(scale: Number): Number
  	// Inverse of `scale()`, returns the zoom level corresponding to a scale
  	// factor of `scale`.
  	zoom: function (scale) {
  		return Math.log(scale / 256) / Math.LN2;
  	},

  	// @method getProjectedBounds(zoom: Number): Bounds
  	// Returns the projection's bounds scaled and transformed for the provided `zoom`.
  	getProjectedBounds: function (zoom) {
  		if (this.infinite) { return null; }

  		var b = this.projection.bounds,
  		    s = this.scale(zoom),
  		    min = this.transformation.transform(b.min, s),
  		    max = this.transformation.transform(b.max, s);

  		return new Bounds(min, max);
  	},

  	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
  	// Returns the distance between two geographical coordinates.

  	// @property code: String
  	// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
  	//
  	// @property wrapLng: Number[]
  	// An array of two numbers defining whether the longitude (horizontal) coordinate
  	// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
  	// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
  	//
  	// @property wrapLat: Number[]
  	// Like `wrapLng`, but for the latitude (vertical) axis.

  	// wrapLng: [min, max],
  	// wrapLat: [min, max],

  	// @property infinite: Boolean
  	// If true, the coordinate space will be unbounded (infinite in both axes)
  	infinite: false,

  	// @method wrapLatLng(latlng: LatLng): LatLng
  	// Returns a `LatLng` where lat and lng has been wrapped according to the
  	// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
  	wrapLatLng: function (latlng) {
  		var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
  		    lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
  		    alt = latlng.alt;

  		return new LatLng(lat, lng, alt);
  	},

  	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
  	// Returns a `LatLngBounds` with the same size as the given one, ensuring
  	// that its center is within the CRS's bounds.
  	// Only accepts actual `L.LatLngBounds` instances, not arrays.
  	wrapLatLngBounds: function (bounds) {
  		var center = bounds.getCenter(),
  		    newCenter = this.wrapLatLng(center),
  		    latShift = center.lat - newCenter.lat,
  		    lngShift = center.lng - newCenter.lng;

  		if (latShift === 0 && lngShift === 0) {
  			return bounds;
  		}

  		var sw = bounds.getSouthWest(),
  		    ne = bounds.getNorthEast(),
  		    newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
  		    newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);

  		return new LatLngBounds(newSw, newNe);
  	}
  };

  /*
   * @namespace CRS
   * @crs L.CRS.Earth
   *
   * Serves as the base for CRS that are global such that they cover the earth.
   * Can only be used as the base for other CRS and cannot be used directly,
   * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
   * meters.
   */

  var Earth = extend({}, CRS, {
  	wrapLng: [-180, 180],

  	// Mean Earth Radius, as recommended for use by
  	// the International Union of Geodesy and Geophysics,
  	// see https://rosettacode.org/wiki/Haversine_formula
  	R: 6371000,

  	// distance between two geographical points using spherical law of cosines approximation
  	distance: function (latlng1, latlng2) {
  		var rad = Math.PI / 180,
  		    lat1 = latlng1.lat * rad,
  		    lat2 = latlng2.lat * rad,
  		    sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
  		    sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
  		    a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
  		    c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  		return this.R * c;
  	}
  });

  /*
   * @namespace Projection
   * @projection L.Projection.SphericalMercator
   *
   * Spherical Mercator projection — the most common projection for online maps,
   * used by almost all free and commercial tile providers. Assumes that Earth is
   * a sphere. Used by the `EPSG:3857` CRS.
   */

  var earthRadius = 6378137;

  var SphericalMercator = {

  	R: earthRadius,
  	MAX_LATITUDE: 85.0511287798,

  	project: function (latlng) {
  		var d = Math.PI / 180,
  		    max = this.MAX_LATITUDE,
  		    lat = Math.max(Math.min(max, latlng.lat), -max),
  		    sin = Math.sin(lat * d);

  		return new Point(
  			this.R * latlng.lng * d,
  			this.R * Math.log((1 + sin) / (1 - sin)) / 2);
  	},

  	unproject: function (point) {
  		var d = 180 / Math.PI;

  		return new LatLng(
  			(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
  			point.x * d / this.R);
  	},

  	bounds: (function () {
  		var d = earthRadius * Math.PI;
  		return new Bounds([-d, -d], [d, d]);
  	})()
  };

  /*
   * @class Transformation
   * @aka L.Transformation
   *
   * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
   * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
   * the reverse. Used by Leaflet in its projections code.
   *
   * @example
   *
   * ```js
   * var transformation = L.transformation(2, 5, -1, 10),
   * 	p = L.point(1, 2),
   * 	p2 = transformation.transform(p), //  L.point(7, 8)
   * 	p3 = transformation.untransform(p2); //  L.point(1, 2)
   * ```
   */


  // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
  // Creates a `Transformation` object with the given coefficients.
  function Transformation(a, b, c, d) {
  	if (isArray(a)) {
  		// use array properties
  		this._a = a[0];
  		this._b = a[1];
  		this._c = a[2];
  		this._d = a[3];
  		return;
  	}
  	this._a = a;
  	this._b = b;
  	this._c = c;
  	this._d = d;
  }

  Transformation.prototype = {
  	// @method transform(point: Point, scale?: Number): Point
  	// Returns a transformed point, optionally multiplied by the given scale.
  	// Only accepts actual `L.Point` instances, not arrays.
  	transform: function (point, scale) { // (Point, Number) -> Point
  		return this._transform(point.clone(), scale);
  	},

  	// destructive transform (faster)
  	_transform: function (point, scale) {
  		scale = scale || 1;
  		point.x = scale * (this._a * point.x + this._b);
  		point.y = scale * (this._c * point.y + this._d);
  		return point;
  	},

  	// @method untransform(point: Point, scale?: Number): Point
  	// Returns the reverse transformation of the given point, optionally divided
  	// by the given scale. Only accepts actual `L.Point` instances, not arrays.
  	untransform: function (point, scale) {
  		scale = scale || 1;
  		return new Point(
  		        (point.x / scale - this._b) / this._a,
  		        (point.y / scale - this._d) / this._c);
  	}
  };

  // factory L.transformation(a: Number, b: Number, c: Number, d: Number)

  // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
  // Instantiates a Transformation object with the given coefficients.

  // @alternative
  // @factory L.transformation(coefficients: Array): Transformation
  // Expects an coefficients array of the form
  // `[a: Number, b: Number, c: Number, d: Number]`.

  function toTransformation(a, b, c, d) {
  	return new Transformation(a, b, c, d);
  }

  /*
   * @namespace CRS
   * @crs L.CRS.EPSG3857
   *
   * The most common CRS for online maps, used by almost all free and commercial
   * tile providers. Uses Spherical Mercator projection. Set in by default in
   * Map's `crs` option.
   */

  var EPSG3857 = extend({}, Earth, {
  	code: 'EPSG:3857',
  	projection: SphericalMercator,

  	transformation: (function () {
  		var scale = 0.5 / (Math.PI * SphericalMercator.R);
  		return toTransformation(scale, 0.5, -scale, 0.5);
  	}())
  });

  var EPSG900913 = extend({}, EPSG3857, {
  	code: 'EPSG:900913'
  });

  // @namespace SVG; @section
  // There are several static functions which can be called without instantiating L.SVG:

  // @function create(name: String): SVGElement
  // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
  // corresponding to the class name passed. For example, using 'line' will return
  // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
  function svgCreate(name) {
  	return document.createElementNS('http://www.w3.org/2000/svg', name);
  }

  // @function pointsToPath(rings: Point[], closed: Boolean): String
  // Generates a SVG path string for multiple rings, with each ring turning
  // into "M..L..L.." instructions
  function pointsToPath(rings, closed) {
  	var str = '',
  	i, j, len, len2, points, p;

  	for (i = 0, len = rings.length; i < len; i++) {
  		points = rings[i];

  		for (j = 0, len2 = points.length; j < len2; j++) {
  			p = points[j];
  			str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
  		}

  		// closes the ring for polygons; "x" is VML syntax
  		str += closed ? (Browser.svg ? 'z' : 'x') : '';
  	}

  	// SVG complains about empty path strings
  	return str || 'M0 0';
  }

  /*
   * @namespace Browser
   * @aka L.Browser
   *
   * A namespace with static properties for browser/feature detection used by Leaflet internally.
   *
   * @example
   *
   * ```js
   * if (L.Browser.ielt9) {
   *   alert('Upgrade your browser, dude!');
   * }
   * ```
   */

  var style = document.documentElement.style;

  // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
  var ie = 'ActiveXObject' in window;

  // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
  var ielt9 = ie && !document.addEventListener;

  // @property edge: Boolean; `true` for the Edge web browser.
  var edge = 'msLaunchUri' in navigator && !('documentMode' in document);

  // @property webkit: Boolean;
  // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
  var webkit = userAgentContains('webkit');

  // @property android: Boolean
  // **Deprecated.** `true` for any browser running on an Android platform.
  var android = userAgentContains('android');

  // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
  var android23 = userAgentContains('android 2') || userAgentContains('android 3');

  /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
  var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
  // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
  var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);

  // @property opera: Boolean; `true` for the Opera browser
  var opera = !!window.opera;

  // @property chrome: Boolean; `true` for the Chrome browser.
  var chrome = !edge && userAgentContains('chrome');

  // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
  var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;

  // @property safari: Boolean; `true` for the Safari browser.
  var safari = !chrome && userAgentContains('safari');

  var phantom = userAgentContains('phantom');

  // @property opera12: Boolean
  // `true` for the Opera browser supporting CSS transforms (version 12 or later).
  var opera12 = 'OTransition' in style;

  // @property win: Boolean; `true` when the browser is running in a Windows platform
  var win = navigator.platform.indexOf('Win') === 0;

  // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
  var ie3d = ie && ('transition' in style);

  // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
  var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;

  // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
  var gecko3d = 'MozPerspective' in style;

  // @property any3d: Boolean
  // `true` for all browsers supporting CSS transforms.
  var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;

  // @property mobile: Boolean; `true` for all browsers running in a mobile device.
  var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');

  // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
  var mobileWebkit = mobile && webkit;

  // @property mobileWebkit3d: Boolean
  // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
  var mobileWebkit3d = mobile && webkit3d;

  // @property msPointer: Boolean
  // `true` for browsers implementing the Microsoft touch events model (notably IE10).
  var msPointer = !window.PointerEvent && window.MSPointerEvent;

  // @property pointer: Boolean
  // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
  var pointer = !!(window.PointerEvent || msPointer);

  // @property touchNative: Boolean
  // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
  // **This does not necessarily mean** that the browser is running in a computer with
  // a touchscreen, it only means that the browser is capable of understanding
  // touch events.
  var touchNative = 'ontouchstart' in window || !!window.TouchEvent;

  // @property touch: Boolean
  // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
  // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
  var touch = !window.L_NO_TOUCH && (touchNative || pointer);

  // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
  var mobileOpera = mobile && opera;

  // @property mobileGecko: Boolean
  // `true` for gecko-based browsers running in a mobile device.
  var mobileGecko = mobile && gecko;

  // @property retina: Boolean
  // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
  var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;

  // @property passiveEvents: Boolean
  // `true` for browsers that support passive events.
  var passiveEvents = (function () {
  	var supportsPassiveOption = false;
  	try {
  		var opts = Object.defineProperty({}, 'passive', {
  			get: function () { // eslint-disable-line getter-return
  				supportsPassiveOption = true;
  			}
  		});
  		window.addEventListener('testPassiveEventSupport', falseFn, opts);
  		window.removeEventListener('testPassiveEventSupport', falseFn, opts);
  	} catch (e) {
  		// Errors can safely be ignored since this is only a browser support test.
  	}
  	return supportsPassiveOption;
  }());

  // @property canvas: Boolean
  // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
  var canvas$1 = (function () {
  	return !!document.createElement('canvas').getContext;
  }());

  // @property svg: Boolean
  // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
  var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);

  var inlineSvg = !!svg$1 && (function () {
  	var div = document.createElement('div');
  	div.innerHTML = '<svg/>';
  	return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
  })();

  // @property vml: Boolean
  // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
  var vml = !svg$1 && (function () {
  	try {
  		var div = document.createElement('div');
  		div.innerHTML = '<v:shape adj="1"/>';

  		var shape = div.firstChild;
  		shape.style.behavior = 'url(#default#VML)';

  		return shape && (typeof shape.adj === 'object');

  	} catch (e) {
  		return false;
  	}
  }());

  function userAgentContains(str) {
  	return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
  }


  var Browser = {
  	ie: ie,
  	ielt9: ielt9,
  	edge: edge,
  	webkit: webkit,
  	android: android,
  	android23: android23,
  	androidStock: androidStock,
  	opera: opera,
  	chrome: chrome,
  	gecko: gecko,
  	safari: safari,
  	phantom: phantom,
  	opera12: opera12,
  	win: win,
  	ie3d: ie3d,
  	webkit3d: webkit3d,
  	gecko3d: gecko3d,
  	any3d: any3d,
  	mobile: mobile,
  	mobileWebkit: mobileWebkit,
  	mobileWebkit3d: mobileWebkit3d,
  	msPointer: msPointer,
  	pointer: pointer,
  	touch: touch,
  	touchNative: touchNative,
  	mobileOpera: mobileOpera,
  	mobileGecko: mobileGecko,
  	retina: retina,
  	passiveEvents: passiveEvents,
  	canvas: canvas$1,
  	svg: svg$1,
  	vml: vml,
  	inlineSvg: inlineSvg
  };

  /*
   * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
   */

  var POINTER_DOWN =   Browser.msPointer ? 'MSPointerDown'   : 'pointerdown';
  var POINTER_MOVE =   Browser.msPointer ? 'MSPointerMove'   : 'pointermove';
  var POINTER_UP =     Browser.msPointer ? 'MSPointerUp'     : 'pointerup';
  var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
  var pEvent = {
  	touchstart  : POINTER_DOWN,
  	touchmove   : POINTER_MOVE,
  	touchend    : POINTER_UP,
  	touchcancel : POINTER_CANCEL
  };
  var handle = {
  	touchstart  : _onPointerStart,
  	touchmove   : _handlePointer,
  	touchend    : _handlePointer,
  	touchcancel : _handlePointer
  };
  var _pointers = {};
  var _pointerDocListener = false;

  // Provides a touch events wrapper for (ms)pointer events.
  // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890

  function addPointerListener(obj, type, handler) {
  	if (type === 'touchstart') {
  		_addPointerDocListener();
  	}
  	if (!handle[type]) {
  		console.warn('wrong event specified:', type);
  		return L.Util.falseFn;
  	}
  	handler = handle[type].bind(this, handler);
  	obj.addEventListener(pEvent[type], handler, false);
  	return handler;
  }

  function removePointerListener(obj, type, handler) {
  	if (!pEvent[type]) {
  		console.warn('wrong event specified:', type);
  		return;
  	}
  	obj.removeEventListener(pEvent[type], handler, false);
  }

  function _globalPointerDown(e) {
  	_pointers[e.pointerId] = e;
  }

  function _globalPointerMove(e) {
  	if (_pointers[e.pointerId]) {
  		_pointers[e.pointerId] = e;
  	}
  }

  function _globalPointerUp(e) {
  	delete _pointers[e.pointerId];
  }

  function _addPointerDocListener() {
  	// need to keep track of what pointers and how many are active to provide e.touches emulation
  	if (!_pointerDocListener) {
  		// we listen document as any drags that end by moving the touch off the screen get fired there
  		document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
  		document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
  		document.addEventListener(POINTER_UP, _globalPointerUp, true);
  		document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);

  		_pointerDocListener = true;
  	}
  }

  function _handlePointer(handler, e) {
  	if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }

  	e.touches = [];
  	for (var i in _pointers) {
  		e.touches.push(_pointers[i]);
  	}
  	e.changedTouches = [e];

  	handler(e);
  }

  function _onPointerStart(handler, e) {
  	// IE10 specific: MsTouch needs preventDefault. See #2000
  	if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
  		preventDefault(e);
  	}
  	_handlePointer(handler, e);
  }

  /*
   * Extends the event handling code with double tap support for mobile browsers.
   *
   * Note: currently most browsers fire native dblclick, with only a few exceptions
   * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
   */

  function makeDblclick(event) {
  	// in modern browsers `type` cannot be just overridden:
  	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
  	var newEvent = {},
  	    prop, i;
  	for (i in event) {
  		prop = event[i];
  		newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
  	}
  	event = newEvent;
  	newEvent.type = 'dblclick';
  	newEvent.detail = 2;
  	newEvent.isTrusted = false;
  	newEvent._simulated = true; // for debug purposes
  	return newEvent;
  }

  var delay = 200;
  function addDoubleTapListener(obj, handler) {
  	// Most browsers handle double tap natively
  	obj.addEventListener('dblclick', handler);

  	// On some platforms the browser doesn't fire native dblclicks for touch events.
  	// It seems that in all such cases `detail` property of `click` event is always `1`.
  	// So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
  	var last = 0,
  	    detail;
  	function simDblclick(e) {
  		if (e.detail !== 1) {
  			detail = e.detail; // keep in sync to avoid false dblclick in some cases
  			return;
  		}

  		if (e.pointerType === 'mouse' ||
  			(e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {

  			return;
  		}

  		var now = Date.now();
  		if (now - last <= delay) {
  			detail++;
  			if (detail === 2) {
  				handler(makeDblclick(e));
  			}
  		} else {
  			detail = 1;
  		}
  		last = now;
  	}

  	obj.addEventListener('click', simDblclick);

  	return {
  		dblclick: handler,
  		simDblclick: simDblclick
  	};
  }

  function removeDoubleTapListener(obj, handlers) {
  	obj.removeEventListener('dblclick', handlers.dblclick);
  	obj.removeEventListener('click', handlers.simDblclick);
  }

  /*
   * @namespace DomUtil
   *
   * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
   * tree, used by Leaflet internally.
   *
   * Most functions expecting or returning a `HTMLElement` also work for
   * SVG elements. The only difference is that classes refer to CSS classes
   * in HTML and SVG classes in SVG.
   */


  // @property TRANSFORM: String
  // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
  var TRANSFORM = testProp(
  	['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);

  // webkitTransition comes first because some browser versions that drop vendor prefix don't do
  // the same for the transitionend event, in particular the Android 4.1 stock browser

  // @property TRANSITION: String
  // Vendor-prefixed transition style name.
  var TRANSITION = testProp(
  	['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);

  // @property TRANSITION_END: String
  // Vendor-prefixed transitionend event name.
  var TRANSITION_END =
  	TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';


  // @function get(id: String|HTMLElement): HTMLElement
  // Returns an element given its DOM id, or returns the element itself
  // if it was passed directly.
  function get(id) {
  	return typeof id === 'string' ? document.getElementById(id) : id;
  }

  // @function getStyle(el: HTMLElement, styleAttrib: String): String
  // Returns the value for a certain style attribute on an element,
  // including computed values or values set through CSS.
  function getStyle(el, style) {
  	var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);

  	if ((!value || value === 'auto') && document.defaultView) {
  		var css = document.defaultView.getComputedStyle(el, null);
  		value = css ? css[style] : null;
  	}
  	return value === 'auto' ? null : value;
  }

  // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
  // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
  function create$1(tagName, className, container) {
  	var el = document.createElement(tagName);
  	el.className = className || '';

  	if (container) {
  		container.appendChild(el);
  	}
  	return el;
  }

  // @function remove(el: HTMLElement)
  // Removes `el` from its parent element
  function remove(el) {
  	var parent = el.parentNode;
  	if (parent) {
  		parent.removeChild(el);
  	}
  }

  // @function empty(el: HTMLElement)
  // Removes all of `el`'s children elements from `el`
  function empty(el) {
  	while (el.firstChild) {
  		el.removeChild(el.firstChild);
  	}
  }

  // @function toFront(el: HTMLElement)
  // Makes `el` the last child of its parent, so it renders in front of the other children.
  function toFront(el) {
  	var parent = el.parentNode;
  	if (parent && parent.lastChild !== el) {
  		parent.appendChild(el);
  	}
  }

  // @function toBack(el: HTMLElement)
  // Makes `el` the first child of its parent, so it renders behind the other children.
  function toBack(el) {
  	var parent = el.parentNode;
  	if (parent && parent.firstChild !== el) {
  		parent.insertBefore(el, parent.firstChild);
  	}
  }

  // @function hasClass(el: HTMLElement, name: String): Boolean
  // Returns `true` if the element's class attribute contains `name`.
  function hasClass(el, name) {
  	if (el.classList !== undefined) {
  		return el.classList.contains(name);
  	}
  	var className = getClass(el);
  	return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
  }

  // @function addClass(el: HTMLElement, name: String)
  // Adds `name` to the element's class attribute.
  function addClass(el, name) {
  	if (el.classList !== undefined) {
  		var classes = splitWords(name);
  		for (var i = 0, len = classes.length; i < len; i++) {
  			el.classList.add(classes[i]);
  		}
  	} else if (!hasClass(el, name)) {
  		var className = getClass(el);
  		setClass(el, (className ? className + ' ' : '') + name);
  	}
  }

  // @function removeClass(el: HTMLElement, name: String)
  // Removes `name` from the element's class attribute.
  function removeClass(el, name) {
  	if (el.classList !== undefined) {
  		el.classList.remove(name);
  	} else {
  		setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
  	}
  }

  // @function setClass(el: HTMLElement, name: String)
  // Sets the element's class.
  function setClass(el, name) {
  	if (el.className.baseVal === undefined) {
  		el.className = name;
  	} else {
  		// in case of SVG element
  		el.className.baseVal = name;
  	}
  }

  // @function getClass(el: HTMLElement): String
  // Returns the element's class.
  function getClass(el) {
  	// Check if the element is an SVGElementInstance and use the correspondingElement instead
  	// (Required for linked SVG elements in IE11.)
  	if (el.correspondingElement) {
  		el = el.correspondingElement;
  	}
  	return el.className.baseVal === undefined ? el.className : el.className.baseVal;
  }

  // @function setOpacity(el: HTMLElement, opacity: Number)
  // Set the opacity of an element (including old IE support).
  // `opacity` must be a number from `0` to `1`.
  function setOpacity(el, value) {
  	if ('opacity' in el.style) {
  		el.style.opacity = value;
  	} else if ('filter' in el.style) {
  		_setOpacityIE(el, value);
  	}
  }

  function _setOpacityIE(el, value) {
  	var filter = false,
  	    filterName = 'DXImageTransform.Microsoft.Alpha';

  	// filters collection throws an error if we try to retrieve a filter that doesn't exist
  	try {
  		filter = el.filters.item(filterName);
  	} catch (e) {
  		// don't set opacity to 1 if we haven't already set an opacity,
  		// it isn't needed and breaks transparent pngs.
  		if (value === 1) { return; }
  	}

  	value = Math.round(value * 100);

  	if (filter) {
  		filter.Enabled = (value !== 100);
  		filter.Opacity = value;
  	} else {
  		el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
  	}
  }

  // @function testProp(props: String[]): String|false
  // Goes through the array of style names and returns the first name
  // that is a valid style name for an element. If no such name is found,
  // it returns false. Useful for vendor-prefixed styles like `transform`.
  function testProp(props) {
  	var style = document.documentElement.style;

  	for (var i = 0; i < props.length; i++) {
  		if (props[i] in style) {
  			return props[i];
  		}
  	}
  	return false;
  }

  // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
  // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
  // and optionally scaled by `scale`. Does not have an effect if the
  // browser doesn't support 3D CSS transforms.
  function setTransform(el, offset, scale) {
  	var pos = offset || new Point(0, 0);

  	el.style[TRANSFORM] =
  		(Browser.ie3d ?
  			'translate(' + pos.x + 'px,' + pos.y + 'px)' :
  			'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
  		(scale ? ' scale(' + scale + ')' : '');
  }

  // @function setPosition(el: HTMLElement, position: Point)
  // Sets the position of `el` to coordinates specified by `position`,
  // using CSS translate or top/left positioning depending on the browser
  // (used by Leaflet internally to position its layers).
  function setPosition(el, point) {

  	/*eslint-disable */
  	el._leaflet_pos = point;
  	/* eslint-enable */

  	if (Browser.any3d) {
  		setTransform(el, point);
  	} else {
  		el.style.left = point.x + 'px';
  		el.style.top = point.y + 'px';
  	}
  }

  // @function getPosition(el: HTMLElement): Point
  // Returns the coordinates of an element previously positioned with setPosition.
  function getPosition(el) {
  	// this method is only used for elements previously positioned using setPosition,
  	// so it's safe to cache the position for performance

  	return el._leaflet_pos || new Point(0, 0);
  }

  // @function disableTextSelection()
  // Prevents the user from generating `selectstart` DOM events, usually generated
  // when the user drags the mouse through a page with text. Used internally
  // by Leaflet to override the behaviour of any click-and-drag interaction on
  // the map. Affects drag interactions on the whole document.

  // @function enableTextSelection()
  // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
  var disableTextSelection;
  var enableTextSelection;
  var _userSelect;
  if ('onselectstart' in document) {
  	disableTextSelection = function () {
  		on(window, 'selectstart', preventDefault);
  	};
  	enableTextSelection = function () {
  		off(window, 'selectstart', preventDefault);
  	};
  } else {
  	var userSelectProperty = testProp(
  		['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);

  	disableTextSelection = function () {
  		if (userSelectProperty) {
  			var style = document.documentElement.style;
  			_userSelect = style[userSelectProperty];
  			style[userSelectProperty] = 'none';
  		}
  	};
  	enableTextSelection = function () {
  		if (userSelectProperty) {
  			document.documentElement.style[userSelectProperty] = _userSelect;
  			_userSelect = undefined;
  		}
  	};
  }

  // @function disableImageDrag()
  // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
  // for `dragstart` DOM events, usually generated when the user drags an image.
  function disableImageDrag() {
  	on(window, 'dragstart', preventDefault);
  }

  // @function enableImageDrag()
  // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
  function enableImageDrag() {
  	off(window, 'dragstart', preventDefault);
  }

  var _outlineElement, _outlineStyle;
  // @function preventOutline(el: HTMLElement)
  // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
  // of the element `el` invisible. Used internally by Leaflet to prevent
  // focusable elements from displaying an outline when the user performs a
  // drag interaction on them.
  function preventOutline(element) {
  	while (element.tabIndex === -1) {
  		element = element.parentNode;
  	}
  	if (!element.style) { return; }
  	restoreOutline();
  	_outlineElement = element;
  	_outlineStyle = element.style.outline;
  	element.style.outline = 'none';
  	on(window, 'keydown', restoreOutline);
  }

  // @function restoreOutline()
  // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
  function restoreOutline() {
  	if (!_outlineElement) { return; }
  	_outlineElement.style.outline = _outlineStyle;
  	_outlineElement = undefined;
  	_outlineStyle = undefined;
  	off(window, 'keydown', restoreOutline);
  }

  // @function getSizedParentNode(el: HTMLElement): HTMLElement
  // Finds the closest parent node which size (width and height) is not null.
  function getSizedParentNode(element) {
  	do {
  		element = element.parentNode;
  	} while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
  	return element;
  }

  // @function getScale(el: HTMLElement): Object
  // Computes the CSS scale currently applied on the element.
  // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
  // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
  function getScale(element) {
  	var rect = element.getBoundingClientRect(); // Read-only in old browsers.

  	return {
  		x: rect.width / element.offsetWidth || 1,
  		y: rect.height / element.offsetHeight || 1,
  		boundingClientRect: rect
  	};
  }

  var DomUtil = {
    __proto__: null,
    TRANSFORM: TRANSFORM,
    TRANSITION: TRANSITION,
    TRANSITION_END: TRANSITION_END,
    get: get,
    getStyle: getStyle,
    create: create$1,
    remove: remove,
    empty: empty,
    toFront: toFront,
    toBack: toBack,
    hasClass: hasClass,
    addClass: addClass,
    removeClass: removeClass,
    setClass: setClass,
    getClass: getClass,
    setOpacity: setOpacity,
    testProp: testProp,
    setTransform: setTransform,
    setPosition: setPosition,
    getPosition: getPosition,
    get disableTextSelection () { return disableTextSelection; },
    get enableTextSelection () { return enableTextSelection; },
    disableImageDrag: disableImageDrag,
    enableImageDrag: enableImageDrag,
    preventOutline: preventOutline,
    restoreOutline: restoreOutline,
    getSizedParentNode: getSizedParentNode,
    getScale: getScale
  };

  /*
   * @namespace DomEvent
   * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
   */

  // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.

  // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
  // Adds a listener function (`fn`) to a particular DOM event type of the
  // element `el`. You can optionally specify the context of the listener
  // (object the `this` keyword will point to). You can also pass several
  // space-separated types (e.g. `'click dblclick'`).

  // @alternative
  // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
  // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
  function on(obj, types, fn, context) {

  	if (types && typeof types === 'object') {
  		for (var type in types) {
  			addOne(obj, type, types[type], fn);
  		}
  	} else {
  		types = splitWords(types);

  		for (var i = 0, len = types.length; i < len; i++) {
  			addOne(obj, types[i], fn, context);
  		}
  	}

  	return this;
  }

  var eventsKey = '_leaflet_events';

  // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
  // Removes a previously added listener function.
  // Note that if you passed a custom context to on, you must pass the same
  // context to `off` in order to remove the listener.

  // @alternative
  // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
  // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`

  // @alternative
  // @function off(el: HTMLElement, types: String): this
  // Removes all previously added listeners of given types.

  // @alternative
  // @function off(el: HTMLElement): this
  // Removes all previously added listeners from given HTMLElement
  function off(obj, types, fn, context) {

  	if (arguments.length === 1) {
  		batchRemove(obj);
  		delete obj[eventsKey];

  	} else if (types && typeof types === 'object') {
  		for (var type in types) {
  			removeOne(obj, type, types[type], fn);
  		}

  	} else {
  		types = splitWords(types);

  		if (arguments.length === 2) {
  			batchRemove(obj, function (type) {
  				return indexOf(types, type) !== -1;
  			});
  		} else {
  			for (var i = 0, len = types.length; i < len; i++) {
  				removeOne(obj, types[i], fn, context);
  			}
  		}
  	}

  	return this;
  }

  function batchRemove(obj, filterFn) {
  	for (var id in obj[eventsKey]) {
  		var type = id.split(/\d/)[0];
  		if (!filterFn || filterFn(type)) {
  			removeOne(obj, type, null, null, id);
  		}
  	}
  }

  var mouseSubst = {
  	mouseenter: 'mouseover',
  	mouseleave: 'mouseout',
  	wheel: !('onwheel' in window) && 'mousewheel'
  };

  function addOne(obj, type, fn, context) {
  	var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');

  	if (obj[eventsKey] && obj[eventsKey][id]) { return this; }

  	var handler = function (e) {
  		return fn.call(context || obj, e || window.event);
  	};

  	var originalHandler = handler;

  	if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
  		// Needs DomEvent.Pointer.js
  		handler = addPointerListener(obj, type, handler);

  	} else if (Browser.touch && (type === 'dblclick')) {
  		handler = addDoubleTapListener(obj, handler);

  	} else if ('addEventListener' in obj) {

  		if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' ||  type === 'mousewheel') {
  			obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);

  		} else if (type === 'mouseenter' || type === 'mouseleave') {
  			handler = function (e) {
  				e = e || window.event;
  				if (isExternalTarget(obj, e)) {
  					originalHandler(e);
  				}
  			};
  			obj.addEventListener(mouseSubst[type], handler, false);

  		} else {
  			obj.addEventListener(type, originalHandler, false);
  		}

  	} else {
  		obj.attachEvent('on' + type, handler);
  	}

  	obj[eventsKey] = obj[eventsKey] || {};
  	obj[eventsKey][id] = handler;
  }

  function removeOne(obj, type, fn, context, id) {
  	id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
  	var handler = obj[eventsKey] && obj[eventsKey][id];

  	if (!handler) { return this; }

  	if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
  		removePointerListener(obj, type, handler);

  	} else if (Browser.touch && (type === 'dblclick')) {
  		removeDoubleTapListener(obj, handler);

  	} else if ('removeEventListener' in obj) {

  		obj.removeEventListener(mouseSubst[type] || type, handler, false);

  	} else {
  		obj.detachEvent('on' + type, handler);
  	}

  	obj[eventsKey][id] = null;
  }

  // @function stopPropagation(ev: DOMEvent): this
  // Stop the given event from propagation to parent elements. Used inside the listener functions:
  // ```js
  // L.DomEvent.on(div, 'click', function (ev) {
  // 	L.DomEvent.stopPropagation(ev);
  // });
  // ```
  function stopPropagation(e) {

  	if (e.stopPropagation) {
  		e.stopPropagation();
  	} else if (e.originalEvent) {  // In case of Leaflet event.
  		e.originalEvent._stopped = true;
  	} else {
  		e.cancelBubble = true;
  	}

  	return this;
  }

  // @function disableScrollPropagation(el: HTMLElement): this
  // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
  function disableScrollPropagation(el) {
  	addOne(el, 'wheel', stopPropagation);
  	return this;
  }

  // @function disableClickPropagation(el: HTMLElement): this
  // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
  // `'mousedown'` and `'touchstart'` events (plus browser variants).
  function disableClickPropagation(el) {
  	on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
  	el['_leaflet_disable_click'] = true;
  	return this;
  }

  // @function preventDefault(ev: DOMEvent): this
  // Prevents the default action of the DOM Event `ev` from happening (such as
  // following a link in the href of the a element, or doing a POST request
  // with page reload when a `<form>` is submitted).
  // Use it inside listener functions.
  function preventDefault(e) {
  	if (e.preventDefault) {
  		e.preventDefault();
  	} else {
  		e.returnValue = false;
  	}
  	return this;
  }

  // @function stop(ev: DOMEvent): this
  // Does `stopPropagation` and `preventDefault` at the same time.
  function stop(e) {
  	preventDefault(e);
  	stopPropagation(e);
  	return this;
  }

  // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
  // Gets normalized mouse position from a DOM event relative to the
  // `container` (border excluded) or to the whole page if not specified.
  function getMousePosition(e, container) {
  	if (!container) {
  		return new Point(e.clientX, e.clientY);
  	}

  	var scale = getScale(container),
  	    offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)

  	return new Point(
  		// offset.left/top values are in page scale (like clientX/Y),
  		// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
  		(e.clientX - offset.left) / scale.x - container.clientLeft,
  		(e.clientY - offset.top) / scale.y - container.clientTop
  	);
  }

  // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
  // and Firefox scrolls device pixels, not CSS pixels
  var wheelPxFactor =
  	(Browser.win && Browser.chrome) ? 2 * window.devicePixelRatio :
  	Browser.gecko ? window.devicePixelRatio : 1;

  // @function getWheelDelta(ev: DOMEvent): Number
  // Gets normalized wheel delta from a wheel DOM event, in vertical
  // pixels scrolled (negative if scrolling down).
  // Events from pointing devices without precise scrolling are mapped to
  // a best guess of 60 pixels.
  function getWheelDelta(e) {
  	return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
  	       (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
  	       (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
  	       (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
  	       (e.deltaX || e.deltaZ) ? 0 :	// Skip horizontal/depth wheel events
  	       e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
  	       (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
  	       e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
  	       0;
  }

  // check if element really left/entered the event target (for mouseenter/mouseleave)
  function isExternalTarget(el, e) {

  	var related = e.relatedTarget;

  	if (!related) { return true; }

  	try {
  		while (related && (related !== el)) {
  			related = related.parentNode;
  		}
  	} catch (err) {
  		return false;
  	}
  	return (related !== el);
  }

  var DomEvent = {
    __proto__: null,
    on: on,
    off: off,
    stopPropagation: stopPropagation,
    disableScrollPropagation: disableScrollPropagation,
    disableClickPropagation: disableClickPropagation,
    preventDefault: preventDefault,
    stop: stop,
    getMousePosition: getMousePosition,
    getWheelDelta: getWheelDelta,
    isExternalTarget: isExternalTarget,
    addListener: on,
    removeListener: off
  };

  /*
   * @class PosAnimation
   * @aka L.PosAnimation
   * @inherits Evented
   * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
   *
   * @example
   * ```js
   * var fx = new L.PosAnimation();
   * fx.run(el, [300, 500], 0.5);
   * ```
   *
   * @constructor L.PosAnimation()
   * Creates a `PosAnimation` object.
   *
   */

  var PosAnimation = Evented.extend({

  	// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
  	// Run an animation of a given element to a new position, optionally setting
  	// duration in seconds (`0.25` by default) and easing linearity factor (3rd
  	// argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
  	// `0.5` by default).
  	run: function (el, newPos, duration, easeLinearity) {
  		this.stop();

  		this._el = el;
  		this._inProgress = true;
  		this._duration = duration || 0.25;
  		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);

  		this._startPos = getPosition(el);
  		this._offset = newPos.subtract(this._startPos);
  		this._startTime = +new Date();

  		// @event start: Event
  		// Fired when the animation starts
  		this.fire('start');

  		this._animate();
  	},

  	// @method stop()
  	// Stops the animation (if currently running).
  	stop: function () {
  		if (!this._inProgress) { return; }

  		this._step(true);
  		this._complete();
  	},

  	_animate: function () {
  		// animation loop
  		this._animId = requestAnimFrame(this._animate, this);
  		this._step();
  	},

  	_step: function (round) {
  		var elapsed = (+new Date()) - this._startTime,
  		    duration = this._duration * 1000;

  		if (elapsed < duration) {
  			this._runFrame(this._easeOut(elapsed / duration), round);
  		} else {
  			this._runFrame(1);
  			this._complete();
  		}
  	},

  	_runFrame: function (progress, round) {
  		var pos = this._startPos.add(this._offset.multiplyBy(progress));
  		if (round) {
  			pos._round();
  		}
  		setPosition(this._el, pos);

  		// @event step: Event
  		// Fired continuously during the animation.
  		this.fire('step');
  	},

  	_complete: function () {
  		cancelAnimFrame(this._animId);

  		this._inProgress = false;
  		// @event end: Event
  		// Fired when the animation ends.
  		this.fire('end');
  	},

  	_easeOut: function (t) {
  		return 1 - Math.pow(1 - t, this._easeOutPower);
  	}
  });

  /*
   * @class Map
   * @aka L.Map
   * @inherits Evented
   *
   * The central class of the API — it is used to create a map on a page and manipulate it.
   *
   * @example
   *
   * ```js
   * // initialize the map on the "map" div with a given center and zoom
   * var map = L.map('map', {
   * 	center: [51.505, -0.09],
   * 	zoom: 13
   * });
   * ```
   *
   */

  var Map = Evented.extend({

  	options: {
  		// @section Map State Options
  		// @option crs: CRS = L.CRS.EPSG3857
  		// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
  		// sure what it means.
  		crs: EPSG3857,

  		// @option center: LatLng = undefined
  		// Initial geographic center of the map
  		center: undefined,

  		// @option zoom: Number = undefined
  		// Initial map zoom level
  		zoom: undefined,

  		// @option minZoom: Number = *
  		// Minimum zoom level of the map.
  		// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
  		// the lowest of their `minZoom` options will be used instead.
  		minZoom: undefined,

  		// @option maxZoom: Number = *
  		// Maximum zoom level of the map.
  		// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
  		// the highest of their `maxZoom` options will be used instead.
  		maxZoom: undefined,

  		// @option layers: Layer[] = []
  		// Array of layers that will be added to the map initially
  		layers: [],

  		// @option maxBounds: LatLngBounds = null
  		// When this option is set, the map restricts the view to the given
  		// geographical bounds, bouncing the user back if the user tries to pan
  		// outside the view. To set the restriction dynamically, use
  		// [`setMaxBounds`](#map-setmaxbounds) method.
  		maxBounds: undefined,

  		// @option renderer: Renderer = *
  		// The default method for drawing vector layers on the map. `L.SVG`
  		// or `L.Canvas` by default depending on browser support.
  		renderer: undefined,


  		// @section Animation Options
  		// @option zoomAnimation: Boolean = true
  		// Whether the map zoom animation is enabled. By default it's enabled
  		// in all browsers that support CSS3 Transitions except Android.
  		zoomAnimation: true,

  		// @option zoomAnimationThreshold: Number = 4
  		// Won't animate zoom if the zoom difference exceeds this value.
  		zoomAnimationThreshold: 4,

  		// @option fadeAnimation: Boolean = true
  		// Whether the tile fade animation is enabled. By default it's enabled
  		// in all browsers that support CSS3 Transitions except Android.
  		fadeAnimation: true,

  		// @option markerZoomAnimation: Boolean = true
  		// Whether markers animate their zoom with the zoom animation, if disabled
  		// they will disappear for the length of the animation. By default it's
  		// enabled in all browsers that support CSS3 Transitions except Android.
  		markerZoomAnimation: true,

  		// @option transform3DLimit: Number = 2^23
  		// Defines the maximum size of a CSS translation transform. The default
  		// value should not be changed unless a web browser positions layers in
  		// the wrong place after doing a large `panBy`.
  		transform3DLimit: 8388608, // Precision limit of a 32-bit float

  		// @section Interaction Options
  		// @option zoomSnap: Number = 1
  		// Forces the map's zoom level to always be a multiple of this, particularly
  		// right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
  		// By default, the zoom level snaps to the nearest integer; lower values
  		// (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
  		// means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
  		zoomSnap: 1,

  		// @option zoomDelta: Number = 1
  		// Controls how much the map's zoom level will change after a
  		// [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
  		// or `-` on the keyboard, or using the [zoom controls](#control-zoom).
  		// Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
  		zoomDelta: 1,

  		// @option trackResize: Boolean = true
  		// Whether the map automatically handles browser window resize to update itself.
  		trackResize: true
  	},

  	initialize: function (id, options) { // (HTMLElement or String, Object)
  		options = setOptions(this, options);

  		// Make sure to assign internal flags at the beginning,
  		// to avoid inconsistent state in some edge cases.
  		this._handlers = [];
  		this._layers = {};
  		this._zoomBoundLayers = {};
  		this._sizeChanged = true;

  		this._initContainer(id);
  		this._initLayout();

  		// hack for https://github.com/Leaflet/Leaflet/issues/1980
  		this._onResize = bind(this._onResize, this);

  		this._initEvents();

  		if (options.maxBounds) {
  			this.setMaxBounds(options.maxBounds);
  		}

  		if (options.zoom !== undefined) {
  			this._zoom = this._limitZoom(options.zoom);
  		}

  		if (options.center && options.zoom !== undefined) {
  			this.setView(toLatLng(options.center), options.zoom, {reset: true});
  		}

  		this.callInitHooks();

  		// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
  		this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
  				this.options.zoomAnimation;

  		// zoom transitions run with the same duration for all layers, so if one of transitionend events
  		// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
  		if (this._zoomAnimated) {
  			this._createAnimProxy();
  			on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
  		}

  		this._addLayers(this.options.layers);
  	},


  	// @section Methods for modifying map state

  	// @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
  	// Sets the view of the map (geographical center and zoom) with the given
  	// animation options.
  	setView: function (center, zoom, options) {

  		zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
  		center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
  		options = options || {};

  		this._stop();

  		if (this._loaded && !options.reset && options !== true) {

  			if (options.animate !== undefined) {
  				options.zoom = extend({animate: options.animate}, options.zoom);
  				options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
  			}

  			// try animating pan or zoom
  			var moved = (this._zoom !== zoom) ?
  				this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
  				this._tryAnimatedPan(center, options.pan);

  			if (moved) {
  				// prevent resize handler call, the view will refresh after animation anyway
  				clearTimeout(this._sizeTimer);
  				return this;
  			}
  		}

  		// animation didn't start, just reset the map view
  		this._resetView(center, zoom);

  		return this;
  	},

  	// @method setZoom(zoom: Number, options?: Zoom/pan options): this
  	// Sets the zoom of the map.
  	setZoom: function (zoom, options) {
  		if (!this._loaded) {
  			this._zoom = zoom;
  			return this;
  		}
  		return this.setView(this.getCenter(), zoom, {zoom: options});
  	},

  	// @method zoomIn(delta?: Number, options?: Zoom options): this
  	// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
  	zoomIn: function (delta, options) {
  		delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
  		return this.setZoom(this._zoom + delta, options);
  	},

  	// @method zoomOut(delta?: Number, options?: Zoom options): this
  	// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
  	zoomOut: function (delta, options) {
  		delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
  		return this.setZoom(this._zoom - delta, options);
  	},

  	// @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
  	// Zooms the map while keeping a specified geographical point on the map
  	// stationary (e.g. used internally for scroll zoom and double-click zoom).
  	// @alternative
  	// @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
  	// Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
  	setZoomAround: function (latlng, zoom, options) {
  		var scale = this.getZoomScale(zoom),
  		    viewHalf = this.getSize().divideBy(2),
  		    containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),

  		    centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
  		    newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));

  		return this.setView(newCenter, zoom, {zoom: options});
  	},

  	_getBoundsCenterZoom: function (bounds, options) {

  		options = options || {};
  		bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);

  		var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
  		    paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),

  		    zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));

  		zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;

  		if (zoom === Infinity) {
  			return {
  				center: bounds.getCenter(),
  				zoom: zoom
  			};
  		}

  		var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),

  		    swPoint = this.project(bounds.getSouthWest(), zoom),
  		    nePoint = this.project(bounds.getNorthEast(), zoom),
  		    center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);

  		return {
  			center: center,
  			zoom: zoom
  		};
  	},

  	// @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
  	// Sets a map view that contains the given geographical bounds with the
  	// maximum zoom level possible.
  	fitBounds: function (bounds, options) {

  		bounds = toLatLngBounds(bounds);

  		if (!bounds.isValid()) {
  			throw new Error('Bounds are not valid.');
  		}

  		var target = this._getBoundsCenterZoom(bounds, options);
  		return this.setView(target.center, target.zoom, options);
  	},

  	// @method fitWorld(options?: fitBounds options): this
  	// Sets a map view that mostly contains the whole world with the maximum
  	// zoom level possible.
  	fitWorld: function (options) {
  		return this.fitBounds([[-90, -180], [90, 180]], options);
  	},

  	// @method panTo(latlng: LatLng, options?: Pan options): this
  	// Pans the map to a given center.
  	panTo: function (center, options) { // (LatLng)
  		return this.setView(center, this._zoom, {pan: options});
  	},

  	// @method panBy(offset: Point, options?: Pan options): this
  	// Pans the map by a given number of pixels (animated).
  	panBy: function (offset, options) {
  		offset = toPoint(offset).round();
  		options = options || {};

  		if (!offset.x && !offset.y) {
  			return this.fire('moveend');
  		}
  		// If we pan too far, Chrome gets issues with tiles
  		// and makes them disappear or appear in the wrong place (slightly offset) #2602
  		if (options.animate !== true && !this.getSize().contains(offset)) {
  			this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
  			return this;
  		}

  		if (!this._panAnim) {
  			this._panAnim = new PosAnimation();

  			this._panAnim.on({
  				'step': this._onPanTransitionStep,
  				'end': this._onPanTransitionEnd
  			}, this);
  		}

  		// don't fire movestart if animating inertia
  		if (!options.noMoveStart) {
  			this.fire('movestart');
  		}

  		// animate pan unless animate: false specified
  		if (options.animate !== false) {
  			addClass(this._mapPane, 'leaflet-pan-anim');

  			var newPos = this._getMapPanePos().subtract(offset).round();
  			this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
  		} else {
  			this._rawPanBy(offset);
  			this.fire('move').fire('moveend');
  		}

  		return this;
  	},

  	// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
  	// Sets the view of the map (geographical center and zoom) performing a smooth
  	// pan-zoom animation.
  	flyTo: function (targetCenter, targetZoom, options) {

  		options = options || {};
  		if (options.animate === false || !Browser.any3d) {
  			return this.setView(targetCenter, targetZoom, options);
  		}

  		this._stop();

  		var from = this.project(this.getCenter()),
  		    to = this.project(targetCenter),
  		    size = this.getSize(),
  		    startZoom = this._zoom;

  		targetCenter = toLatLng(targetCenter);
  		targetZoom = targetZoom === undefined ? startZoom : targetZoom;

  		var w0 = Math.max(size.x, size.y),
  		    w1 = w0 * this.getZoomScale(startZoom, targetZoom),
  		    u1 = (to.distanceTo(from)) || 1,
  		    rho = 1.42,
  		    rho2 = rho * rho;

  		function r(i) {
  			var s1 = i ? -1 : 1,
  			    s2 = i ? w1 : w0,
  			    t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
  			    b1 = 2 * s2 * rho2 * u1,
  			    b = t1 / b1,
  			    sq = Math.sqrt(b * b + 1) - b;

  			    // workaround for floating point precision bug when sq = 0, log = -Infinite,
  			    // thus triggering an infinite loop in flyTo
  			    var log = sq < 0.000000001 ? -18 : Math.log(sq);

  			return log;
  		}

  		function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
  		function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
  		function tanh(n) { return sinh(n) / cosh(n); }

  		var r0 = r(0);

  		function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
  		function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }

  		function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }

  		var start = Date.now(),
  		    S = (r(1) - r0) / rho,
  		    duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;

  		function frame() {
  			var t = (Date.now() - start) / duration,
  			    s = easeOut(t) * S;

  			if (t <= 1) {
  				this._flyToFrame = requestAnimFrame(frame, this);

  				this._move(
  					this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
  					this.getScaleZoom(w0 / w(s), startZoom),
  					{flyTo: true});

  			} else {
  				this
  					._move(targetCenter, targetZoom)
  					._moveEnd(true);
  			}
  		}

  		this._moveStart(true, options.noMoveStart);

  		frame.call(this);
  		return this;
  	},

  	// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
  	// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
  	// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
  	flyToBounds: function (bounds, options) {
  		var target = this._getBoundsCenterZoom(bounds, options);
  		return this.flyTo(target.center, target.zoom, options);
  	},

  	// @method setMaxBounds(bounds: LatLngBounds): this
  	// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
  	setMaxBounds: function (bounds) {
  		bounds = toLatLngBounds(bounds);

  		if (!bounds.isValid()) {
  			this.options.maxBounds = null;
  			return this.off('moveend', this._panInsideMaxBounds);
  		} else if (this.options.maxBounds) {
  			this.off('moveend', this._panInsideMaxBounds);
  		}

  		this.options.maxBounds = bounds;

  		if (this._loaded) {
  			this._panInsideMaxBounds();
  		}

  		return this.on('moveend', this._panInsideMaxBounds);
  	},

  	// @method setMinZoom(zoom: Number): this
  	// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
  	setMinZoom: function (zoom) {
  		var oldZoom = this.options.minZoom;
  		this.options.minZoom = zoom;

  		if (this._loaded && oldZoom !== zoom) {
  			this.fire('zoomlevelschange');

  			if (this.getZoom() < this.options.minZoom) {
  				return this.setZoom(zoom);
  			}
  		}

  		return this;
  	},

  	// @method setMaxZoom(zoom: Number): this
  	// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
  	setMaxZoom: function (zoom) {
  		var oldZoom = this.options.maxZoom;
  		this.options.maxZoom = zoom;

  		if (this._loaded && oldZoom !== zoom) {
  			this.fire('zoomlevelschange');

  			if (this.getZoom() > this.options.maxZoom) {
  				return this.setZoom(zoom);
  			}
  		}

  		return this;
  	},

  	// @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
  	// Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
  	panInsideBounds: function (bounds, options) {
  		this._enforcingBounds = true;
  		var center = this.getCenter(),
  		    newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));

  		if (!center.equals(newCenter)) {
  			this.panTo(newCenter, options);
  		}

  		this._enforcingBounds = false;
  		return this;
  	},

  	// @method panInside(latlng: LatLng, options?: padding options): this
  	// Pans the map the minimum amount to make the `latlng` visible. Use
  	// padding options to fit the display to more restricted bounds.
  	// If `latlng` is already within the (optionally padded) display bounds,
  	// the map will not be panned.
  	panInside: function (latlng, options) {
  		options = options || {};

  		var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
  		    paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
  		    pixelCenter = this.project(this.getCenter()),
  		    pixelPoint = this.project(latlng),
  		    pixelBounds = this.getPixelBounds(),
  		    paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
  		    paddedSize = paddedBounds.getSize();

  		if (!paddedBounds.contains(pixelPoint)) {
  			this._enforcingBounds = true;
  			var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
  			var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
  			pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
  			pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
  			this.panTo(this.unproject(pixelCenter), options);
  			this._enforcingBounds = false;
  		}
  		return this;
  	},

  	// @method invalidateSize(options: Zoom/pan options): this
  	// Checks if the map container size changed and updates the map if so —
  	// call it after you've changed the map size dynamically, also animating
  	// pan by default. If `options.pan` is `false`, panning will not occur.
  	// If `options.debounceMoveend` is `true`, it will delay `moveend` event so
  	// that it doesn't happen often even if the method is called many
  	// times in a row.

  	// @alternative
  	// @method invalidateSize(animate: Boolean): this
  	// Checks if the map container size changed and updates the map if so —
  	// call it after you've changed the map size dynamically, also animating
  	// pan by default.
  	invalidateSize: function (options) {
  		if (!this._loaded) { return this; }

  		options = extend({
  			animate: false,
  			pan: true
  		}, options === true ? {animate: true} : options);

  		var oldSize = this.getSize();
  		this._sizeChanged = true;
  		this._lastCenter = null;

  		var newSize = this.getSize(),
  		    oldCenter = oldSize.divideBy(2).round(),
  		    newCenter = newSize.divideBy(2).round(),
  		    offset = oldCenter.subtract(newCenter);

  		if (!offset.x && !offset.y) { return this; }

  		if (options.animate && options.pan) {
  			this.panBy(offset);

  		} else {
  			if (options.pan) {
  				this._rawPanBy(offset);
  			}

  			this.fire('move');

  			if (options.debounceMoveend) {
  				clearTimeout(this._sizeTimer);
  				this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
  			} else {
  				this.fire('moveend');
  			}
  		}

  		// @section Map state change events
  		// @event resize: ResizeEvent
  		// Fired when the map is resized.
  		return this.fire('resize', {
  			oldSize: oldSize,
  			newSize: newSize
  		});
  	},

  	// @section Methods for modifying map state
  	// @method stop(): this
  	// Stops the currently running `panTo` or `flyTo` animation, if any.
  	stop: function () {
  		this.setZoom(this._limitZoom(this._zoom));
  		if (!this.options.zoomSnap) {
  			this.fire('viewreset');
  		}
  		return this._stop();
  	},

  	// @section Geolocation methods
  	// @method locate(options?: Locate options): this
  	// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
  	// event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
  	// and optionally sets the map view to the user's location with respect to
  	// detection accuracy (or to the world view if geolocation failed).
  	// Note that, if your page doesn't use HTTPS, this method will fail in
  	// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
  	// See `Locate options` for more details.
  	locate: function (options) {

  		options = this._locateOptions = extend({
  			timeout: 10000,
  			watch: false
  			// setView: false
  			// maxZoom: <Number>
  			// maximumAge: 0
  			// enableHighAccuracy: false
  		}, options);

  		if (!('geolocation' in navigator)) {
  			this._handleGeolocationError({
  				code: 0,
  				message: 'Geolocation not supported.'
  			});
  			return this;
  		}

  		var onResponse = bind(this._handleGeolocationResponse, this),
  		    onError = bind(this._handleGeolocationError, this);

  		if (options.watch) {
  			this._locationWatchId =
  			        navigator.geolocation.watchPosition(onResponse, onError, options);
  		} else {
  			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
  		}
  		return this;
  	},

  	// @method stopLocate(): this
  	// Stops watching location previously initiated by `map.locate({watch: true})`
  	// and aborts resetting the map view if map.locate was called with
  	// `{setView: true}`.
  	stopLocate: function () {
  		if (navigator.geolocation && navigator.geolocation.clearWatch) {
  			navigator.geolocation.clearWatch(this._locationWatchId);
  		}
  		if (this._locateOptions) {
  			this._locateOptions.setView = false;
  		}
  		return this;
  	},

  	_handleGeolocationError: function (error) {
  		if (!this._container._leaflet_id) { return; }

  		var c = error.code,
  		    message = error.message ||
  		            (c === 1 ? 'permission denied' :
  		            (c === 2 ? 'position unavailable' : 'timeout'));

  		if (this._locateOptions.setView && !this._loaded) {
  			this.fitWorld();
  		}

  		// @section Location events
  		// @event locationerror: ErrorEvent
  		// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
  		this.fire('locationerror', {
  			code: c,
  			message: 'Geolocation error: ' + message + '.'
  		});
  	},

  	_handleGeolocationResponse: function (pos) {
  		if (!this._container._leaflet_id) { return; }

  		var lat = pos.coords.latitude,
  		    lng = pos.coords.longitude,
  		    latlng = new LatLng(lat, lng),
  		    bounds = latlng.toBounds(pos.coords.accuracy * 2),
  		    options = this._locateOptions;

  		if (options.setView) {
  			var zoom = this.getBoundsZoom(bounds);
  			this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
  		}

  		var data = {
  			latlng: latlng,
  			bounds: bounds,
  			timestamp: pos.timestamp
  		};

  		for (var i in pos.coords) {
  			if (typeof pos.coords[i] === 'number') {
  				data[i] = pos.coords[i];
  			}
  		}

  		// @event locationfound: LocationEvent
  		// Fired when geolocation (using the [`locate`](#map-locate) method)
  		// went successfully.
  		this.fire('locationfound', data);
  	},

  	// TODO Appropriate docs section?
  	// @section Other Methods
  	// @method addHandler(name: String, HandlerClass: Function): this
  	// Adds a new `Handler` to the map, given its name and constructor function.
  	addHandler: function (name, HandlerClass) {
  		if (!HandlerClass) { return this; }

  		var handler = this[name] = new HandlerClass(this);

  		this._handlers.push(handler);

  		if (this.options[name]) {
  			handler.enable();
  		}

  		return this;
  	},

  	// @method remove(): this
  	// Destroys the map and clears all related event listeners.
  	remove: function () {

  		this._initEvents(true);
  		if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }

  		if (this._containerId !== this._container._leaflet_id) {
  			throw new Error('Map container is being reused by another instance');
  		}

  		try {
  			// throws error in IE6-8
  			delete this._container._leaflet_id;
  			delete this._containerId;
  		} catch (e) {
  			/*eslint-disable */
  			this._container._leaflet_id = undefined;
  			/* eslint-enable */
  			this._containerId = undefined;
  		}

  		if (this._locationWatchId !== undefined) {
  			this.stopLocate();
  		}

  		this._stop();

  		remove(this._mapPane);

  		if (this._clearControlPos) {
  			this._clearControlPos();
  		}
  		if (this._resizeRequest) {
  			cancelAnimFrame(this._resizeRequest);
  			this._resizeRequest = null;
  		}

  		this._clearHandlers();

  		if (this._loaded) {
  			// @section Map state change events
  			// @event unload: Event
  			// Fired when the map is destroyed with [remove](#map-remove) method.
  			this.fire('unload');
  		}

  		var i;
  		for (i in this._layers) {
  			this._layers[i].remove();
  		}
  		for (i in this._panes) {
  			remove(this._panes[i]);
  		}

  		this._layers = [];
  		this._panes = [];
  		delete this._mapPane;
  		delete this._renderer;

  		return this;
  	},

  	// @section Other Methods
  	// @method createPane(name: String, container?: HTMLElement): HTMLElement
  	// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
  	// then returns it. The pane is created as a child of `container`, or
  	// as a child of the main map pane if not set.
  	createPane: function (name, container) {
  		var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
  		    pane = create$1('div', className, container || this._mapPane);

  		if (name) {
  			this._panes[name] = pane;
  		}
  		return pane;
  	},

  	// @section Methods for Getting Map State

  	// @method getCenter(): LatLng
  	// Returns the geographical center of the map view
  	getCenter: function () {
  		this._checkIfLoaded();

  		if (this._lastCenter && !this._moved()) {
  			return this._lastCenter;
  		}
  		return this.layerPointToLatLng(this._getCenterLayerPoint());
  	},

  	// @method getZoom(): Number
  	// Returns the current zoom level of the map view
  	getZoom: function () {
  		return this._zoom;
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the geographical bounds visible in the current map view
  	getBounds: function () {
  		var bounds = this.getPixelBounds(),
  		    sw = this.unproject(bounds.getBottomLeft()),
  		    ne = this.unproject(bounds.getTopRight());

  		return new LatLngBounds(sw, ne);
  	},

  	// @method getMinZoom(): Number
  	// Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
  	getMinZoom: function () {
  		return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
  	},

  	// @method getMaxZoom(): Number
  	// Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
  	getMaxZoom: function () {
  		return this.options.maxZoom === undefined ?
  			(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
  			this.options.maxZoom;
  	},

  	// @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
  	// Returns the maximum zoom level on which the given bounds fit to the map
  	// view in its entirety. If `inside` (optional) is set to `true`, the method
  	// instead returns the minimum zoom level on which the map view fits into
  	// the given bounds in its entirety.
  	getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
  		bounds = toLatLngBounds(bounds);
  		padding = toPoint(padding || [0, 0]);

  		var zoom = this.getZoom() || 0,
  		    min = this.getMinZoom(),
  		    max = this.getMaxZoom(),
  		    nw = bounds.getNorthWest(),
  		    se = bounds.getSouthEast(),
  		    size = this.getSize().subtract(padding),
  		    boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
  		    snap = Browser.any3d ? this.options.zoomSnap : 1,
  		    scalex = size.x / boundsSize.x,
  		    scaley = size.y / boundsSize.y,
  		    scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);

  		zoom = this.getScaleZoom(scale, zoom);

  		if (snap) {
  			zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
  			zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
  		}

  		return Math.max(min, Math.min(max, zoom));
  	},

  	// @method getSize(): Point
  	// Returns the current size of the map container (in pixels).
  	getSize: function () {
  		if (!this._size || this._sizeChanged) {
  			this._size = new Point(
  				this._container.clientWidth || 0,
  				this._container.clientHeight || 0);

  			this._sizeChanged = false;
  		}
  		return this._size.clone();
  	},

  	// @method getPixelBounds(): Bounds
  	// Returns the bounds of the current map view in projected pixel
  	// coordinates (sometimes useful in layer and overlay implementations).
  	getPixelBounds: function (center, zoom) {
  		var topLeftPoint = this._getTopLeftPoint(center, zoom);
  		return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
  	},

  	// TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
  	// the map pane? "left point of the map layer" can be confusing, specially
  	// since there can be negative offsets.
  	// @method getPixelOrigin(): Point
  	// Returns the projected pixel coordinates of the top left point of
  	// the map layer (useful in custom layer and overlay implementations).
  	getPixelOrigin: function () {
  		this._checkIfLoaded();
  		return this._pixelOrigin;
  	},

  	// @method getPixelWorldBounds(zoom?: Number): Bounds
  	// Returns the world's bounds in pixel coordinates for zoom level `zoom`.
  	// If `zoom` is omitted, the map's current zoom level is used.
  	getPixelWorldBounds: function (zoom) {
  		return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
  	},

  	// @section Other Methods

  	// @method getPane(pane: String|HTMLElement): HTMLElement
  	// Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
  	getPane: function (pane) {
  		return typeof pane === 'string' ? this._panes[pane] : pane;
  	},

  	// @method getPanes(): Object
  	// Returns a plain object containing the names of all [panes](#map-pane) as keys and
  	// the panes as values.
  	getPanes: function () {
  		return this._panes;
  	},

  	// @method getContainer: HTMLElement
  	// Returns the HTML element that contains the map.
  	getContainer: function () {
  		return this._container;
  	},


  	// @section Conversion Methods

  	// @method getZoomScale(toZoom: Number, fromZoom: Number): Number
  	// Returns the scale factor to be applied to a map transition from zoom level
  	// `fromZoom` to `toZoom`. Used internally to help with zoom animations.
  	getZoomScale: function (toZoom, fromZoom) {
  		// TODO replace with universal implementation after refactoring projections
  		var crs = this.options.crs;
  		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
  		return crs.scale(toZoom) / crs.scale(fromZoom);
  	},

  	// @method getScaleZoom(scale: Number, fromZoom: Number): Number
  	// Returns the zoom level that the map would end up at, if it is at `fromZoom`
  	// level and everything is scaled by a factor of `scale`. Inverse of
  	// [`getZoomScale`](#map-getZoomScale).
  	getScaleZoom: function (scale, fromZoom) {
  		var crs = this.options.crs;
  		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
  		var zoom = crs.zoom(scale * crs.scale(fromZoom));
  		return isNaN(zoom) ? Infinity : zoom;
  	},

  	// @method project(latlng: LatLng, zoom: Number): Point
  	// Projects a geographical coordinate `LatLng` according to the projection
  	// of the map's CRS, then scales it according to `zoom` and the CRS's
  	// `Transformation`. The result is pixel coordinate relative to
  	// the CRS origin.
  	project: function (latlng, zoom) {
  		zoom = zoom === undefined ? this._zoom : zoom;
  		return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
  	},

  	// @method unproject(point: Point, zoom: Number): LatLng
  	// Inverse of [`project`](#map-project).
  	unproject: function (point, zoom) {
  		zoom = zoom === undefined ? this._zoom : zoom;
  		return this.options.crs.pointToLatLng(toPoint(point), zoom);
  	},

  	// @method layerPointToLatLng(point: Point): LatLng
  	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
  	// returns the corresponding geographical coordinate (for the current zoom level).
  	layerPointToLatLng: function (point) {
  		var projectedPoint = toPoint(point).add(this.getPixelOrigin());
  		return this.unproject(projectedPoint);
  	},

  	// @method latLngToLayerPoint(latlng: LatLng): Point
  	// Given a geographical coordinate, returns the corresponding pixel coordinate
  	// relative to the [origin pixel](#map-getpixelorigin).
  	latLngToLayerPoint: function (latlng) {
  		var projectedPoint = this.project(toLatLng(latlng))._round();
  		return projectedPoint._subtract(this.getPixelOrigin());
  	},

  	// @method wrapLatLng(latlng: LatLng): LatLng
  	// Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
  	// map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
  	// CRS's bounds.
  	// By default this means longitude is wrapped around the dateline so its
  	// value is between -180 and +180 degrees.
  	wrapLatLng: function (latlng) {
  		return this.options.crs.wrapLatLng(toLatLng(latlng));
  	},

  	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
  	// Returns a `LatLngBounds` with the same size as the given one, ensuring that
  	// its center is within the CRS's bounds.
  	// By default this means the center longitude is wrapped around the dateline so its
  	// value is between -180 and +180 degrees, and the majority of the bounds
  	// overlaps the CRS's bounds.
  	wrapLatLngBounds: function (latlng) {
  		return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
  	},

  	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
  	// Returns the distance between two geographical coordinates according to
  	// the map's CRS. By default this measures distance in meters.
  	distance: function (latlng1, latlng2) {
  		return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
  	},

  	// @method containerPointToLayerPoint(point: Point): Point
  	// Given a pixel coordinate relative to the map container, returns the corresponding
  	// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
  	containerPointToLayerPoint: function (point) { // (Point)
  		return toPoint(point).subtract(this._getMapPanePos());
  	},

  	// @method layerPointToContainerPoint(point: Point): Point
  	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
  	// returns the corresponding pixel coordinate relative to the map container.
  	layerPointToContainerPoint: function (point) { // (Point)
  		return toPoint(point).add(this._getMapPanePos());
  	},

  	// @method containerPointToLatLng(point: Point): LatLng
  	// Given a pixel coordinate relative to the map container, returns
  	// the corresponding geographical coordinate (for the current zoom level).
  	containerPointToLatLng: function (point) {
  		var layerPoint = this.containerPointToLayerPoint(toPoint(point));
  		return this.layerPointToLatLng(layerPoint);
  	},

  	// @method latLngToContainerPoint(latlng: LatLng): Point
  	// Given a geographical coordinate, returns the corresponding pixel coordinate
  	// relative to the map container.
  	latLngToContainerPoint: function (latlng) {
  		return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
  	},

  	// @method mouseEventToContainerPoint(ev: MouseEvent): Point
  	// Given a MouseEvent object, returns the pixel coordinate relative to the
  	// map container where the event took place.
  	mouseEventToContainerPoint: function (e) {
  		return getMousePosition(e, this._container);
  	},

  	// @method mouseEventToLayerPoint(ev: MouseEvent): Point
  	// Given a MouseEvent object, returns the pixel coordinate relative to
  	// the [origin pixel](#map-getpixelorigin) where the event took place.
  	mouseEventToLayerPoint: function (e) {
  		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
  	},

  	// @method mouseEventToLatLng(ev: MouseEvent): LatLng
  	// Given a MouseEvent object, returns geographical coordinate where the
  	// event took place.
  	mouseEventToLatLng: function (e) { // (MouseEvent)
  		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
  	},


  	// map initialization methods

  	_initContainer: function (id) {
  		var container = this._container = get(id);

  		if (!container) {
  			throw new Error('Map container not found.');
  		} else if (container._leaflet_id) {
  			throw new Error('Map container is already initialized.');
  		}

  		on(container, 'scroll', this._onScroll, this);
  		this._containerId = stamp(container);
  	},

  	_initLayout: function () {
  		var container = this._container;

  		this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;

  		addClass(container, 'leaflet-container' +
  			(Browser.touch ? ' leaflet-touch' : '') +
  			(Browser.retina ? ' leaflet-retina' : '') +
  			(Browser.ielt9 ? ' leaflet-oldie' : '') +
  			(Browser.safari ? ' leaflet-safari' : '') +
  			(this._fadeAnimated ? ' leaflet-fade-anim' : ''));

  		var position = getStyle(container, 'position');

  		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
  			container.style.position = 'relative';
  		}

  		this._initPanes();

  		if (this._initControlPos) {
  			this._initControlPos();
  		}
  	},

  	_initPanes: function () {
  		var panes = this._panes = {};
  		this._paneRenderers = {};

  		// @section
  		//
  		// Panes are DOM elements used to control the ordering of layers on the map. You
  		// can access panes with [`map.getPane`](#map-getpane) or
  		// [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
  		// [`map.createPane`](#map-createpane) method.
  		//
  		// Every map has the following default panes that differ only in zIndex.
  		//
  		// @pane mapPane: HTMLElement = 'auto'
  		// Pane that contains all other map panes

  		this._mapPane = this.createPane('mapPane', this._container);
  		setPosition(this._mapPane, new Point(0, 0));

  		// @pane tilePane: HTMLElement = 200
  		// Pane for `GridLayer`s and `TileLayer`s
  		this.createPane('tilePane');
  		// @pane overlayPane: HTMLElement = 400
  		// Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
  		this.createPane('overlayPane');
  		// @pane shadowPane: HTMLElement = 500
  		// Pane for overlay shadows (e.g. `Marker` shadows)
  		this.createPane('shadowPane');
  		// @pane markerPane: HTMLElement = 600
  		// Pane for `Icon`s of `Marker`s
  		this.createPane('markerPane');
  		// @pane tooltipPane: HTMLElement = 650
  		// Pane for `Tooltip`s.
  		this.createPane('tooltipPane');
  		// @pane popupPane: HTMLElement = 700
  		// Pane for `Popup`s.
  		this.createPane('popupPane');

  		if (!this.options.markerZoomAnimation) {
  			addClass(panes.markerPane, 'leaflet-zoom-hide');
  			addClass(panes.shadowPane, 'leaflet-zoom-hide');
  		}
  	},


  	// private methods that modify map state

  	// @section Map state change events
  	_resetView: function (center, zoom) {
  		setPosition(this._mapPane, new Point(0, 0));

  		var loading = !this._loaded;
  		this._loaded = true;
  		zoom = this._limitZoom(zoom);

  		this.fire('viewprereset');

  		var zoomChanged = this._zoom !== zoom;
  		this
  			._moveStart(zoomChanged, false)
  			._move(center, zoom)
  			._moveEnd(zoomChanged);

  		// @event viewreset: Event
  		// Fired when the map needs to redraw its content (this usually happens
  		// on map zoom or load). Very useful for creating custom overlays.
  		this.fire('viewreset');

  		// @event load: Event
  		// Fired when the map is initialized (when its center and zoom are set
  		// for the first time).
  		if (loading) {
  			this.fire('load');
  		}
  	},

  	_moveStart: function (zoomChanged, noMoveStart) {
  		// @event zoomstart: Event
  		// Fired when the map zoom is about to change (e.g. before zoom animation).
  		// @event movestart: Event
  		// Fired when the view of the map starts changing (e.g. user starts dragging the map).
  		if (zoomChanged) {
  			this.fire('zoomstart');
  		}
  		if (!noMoveStart) {
  			this.fire('movestart');
  		}
  		return this;
  	},

  	_move: function (center, zoom, data, supressEvent) {
  		if (zoom === undefined) {
  			zoom = this._zoom;
  		}
  		var zoomChanged = this._zoom !== zoom;

  		this._zoom = zoom;
  		this._lastCenter = center;
  		this._pixelOrigin = this._getNewPixelOrigin(center);

  		if (!supressEvent) {
  			// @event zoom: Event
  			// Fired repeatedly during any change in zoom level,
  			// including zoom and fly animations.
  			if (zoomChanged || (data && data.pinch)) {	// Always fire 'zoom' if pinching because #3530
  				this.fire('zoom', data);
  			}

  			// @event move: Event
  			// Fired repeatedly during any movement of the map,
  			// including pan and fly animations.
  			this.fire('move', data);
  		} else if (data && data.pinch) {	// Always fire 'zoom' if pinching because #3530
  			this.fire('zoom', data);
  		}
  		return this;
  	},

  	_moveEnd: function (zoomChanged) {
  		// @event zoomend: Event
  		// Fired when the map zoom changed, after any animations.
  		if (zoomChanged) {
  			this.fire('zoomend');
  		}

  		// @event moveend: Event
  		// Fired when the center of the map stops changing
  		// (e.g. user stopped dragging the map or after non-centered zoom).
  		return this.fire('moveend');
  	},

  	_stop: function () {
  		cancelAnimFrame(this._flyToFrame);
  		if (this._panAnim) {
  			this._panAnim.stop();
  		}
  		return this;
  	},

  	_rawPanBy: function (offset) {
  		setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
  	},

  	_getZoomSpan: function () {
  		return this.getMaxZoom() - this.getMinZoom();
  	},

  	_panInsideMaxBounds: function () {
  		if (!this._enforcingBounds) {
  			this.panInsideBounds(this.options.maxBounds);
  		}
  	},

  	_checkIfLoaded: function () {
  		if (!this._loaded) {
  			throw new Error('Set map center and zoom first.');
  		}
  	},

  	// DOM event handling

  	// @section Interaction events
  	_initEvents: function (remove) {
  		this._targets = {};
  		this._targets[stamp(this._container)] = this;

  		var onOff = remove ? off : on;

  		// @event click: MouseEvent
  		// Fired when the user clicks (or taps) the map.
  		// @event dblclick: MouseEvent
  		// Fired when the user double-clicks (or double-taps) the map.
  		// @event mousedown: MouseEvent
  		// Fired when the user pushes the mouse button on the map.
  		// @event mouseup: MouseEvent
  		// Fired when the user releases the mouse button on the map.
  		// @event mouseover: MouseEvent
  		// Fired when the mouse enters the map.
  		// @event mouseout: MouseEvent
  		// Fired when the mouse leaves the map.
  		// @event mousemove: MouseEvent
  		// Fired while the mouse moves over the map.
  		// @event contextmenu: MouseEvent
  		// Fired when the user pushes the right mouse button on the map, prevents
  		// default browser context menu from showing if there are listeners on
  		// this event. Also fired on mobile when the user holds a single touch
  		// for a second (also called long press).
  		// @event keypress: KeyboardEvent
  		// Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
  		// @event keydown: KeyboardEvent
  		// Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
  		// the `keydown` event is fired for keys that produce a character value and for keys
  		// that do not produce a character value.
  		// @event keyup: KeyboardEvent
  		// Fired when the user releases a key from the keyboard while the map is focused.
  		onOff(this._container, 'click dblclick mousedown mouseup ' +
  			'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);

  		if (this.options.trackResize) {
  			onOff(window, 'resize', this._onResize, this);
  		}

  		if (Browser.any3d && this.options.transform3DLimit) {
  			(remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
  		}
  	},

  	_onResize: function () {
  		cancelAnimFrame(this._resizeRequest);
  		this._resizeRequest = requestAnimFrame(
  		        function () { this.invalidateSize({debounceMoveend: true}); }, this);
  	},

  	_onScroll: function () {
  		this._container.scrollTop  = 0;
  		this._container.scrollLeft = 0;
  	},

  	_onMoveEnd: function () {
  		var pos = this._getMapPanePos();
  		if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
  			// https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
  			// a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
  			this._resetView(this.getCenter(), this.getZoom());
  		}
  	},

  	_findEventTargets: function (e, type) {
  		var targets = [],
  		    target,
  		    isHover = type === 'mouseout' || type === 'mouseover',
  		    src = e.target || e.srcElement,
  		    dragging = false;

  		while (src) {
  			target = this._targets[stamp(src)];
  			if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
  				// Prevent firing click after you just dragged an object.
  				dragging = true;
  				break;
  			}
  			if (target && target.listens(type, true)) {
  				if (isHover && !isExternalTarget(src, e)) { break; }
  				targets.push(target);
  				if (isHover) { break; }
  			}
  			if (src === this._container) { break; }
  			src = src.parentNode;
  		}
  		if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
  			targets = [this];
  		}
  		return targets;
  	},

  	_isClickDisabled: function (el) {
  		while (el !== this._container) {
  			if (el['_leaflet_disable_click']) { return true; }
  			el = el.parentNode;
  		}
  	},

  	_handleDOMEvent: function (e) {
  		var el = (e.target || e.srcElement);
  		if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
  			return;
  		}

  		var type = e.type;

  		if (type === 'mousedown') {
  			// prevents outline when clicking on keyboard-focusable element
  			preventOutline(el);
  		}

  		this._fireDOMEvent(e, type);
  	},

  	_mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],

  	_fireDOMEvent: function (e, type, canvasTargets) {

  		if (e.type === 'click') {
  			// Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
  			// @event preclick: MouseEvent
  			// Fired before mouse click on the map (sometimes useful when you
  			// want something to happen on click before any existing click
  			// handlers start running).
  			var synth = extend({}, e);
  			synth.type = 'preclick';
  			this._fireDOMEvent(synth, synth.type, canvasTargets);
  		}

  		// Find the layer the event is propagating from and its parents.
  		var targets = this._findEventTargets(e, type);

  		if (canvasTargets) {
  			var filtered = []; // pick only targets with listeners
  			for (var i = 0; i < canvasTargets.length; i++) {
  				if (canvasTargets[i].listens(type, true)) {
  					filtered.push(canvasTargets[i]);
  				}
  			}
  			targets = filtered.concat(targets);
  		}

  		if (!targets.length) { return; }

  		if (type === 'contextmenu') {
  			preventDefault(e);
  		}

  		var target = targets[0];
  		var data = {
  			originalEvent: e
  		};

  		if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
  			var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
  			data.containerPoint = isMarker ?
  				this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
  			data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
  			data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
  		}

  		for (i = 0; i < targets.length; i++) {
  			targets[i].fire(type, data, true);
  			if (data.originalEvent._stopped ||
  				(targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
  		}
  	},

  	_draggableMoved: function (obj) {
  		obj = obj.dragging && obj.dragging.enabled() ? obj : this;
  		return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
  	},

  	_clearHandlers: function () {
  		for (var i = 0, len = this._handlers.length; i < len; i++) {
  			this._handlers[i].disable();
  		}
  	},

  	// @section Other Methods

  	// @method whenReady(fn: Function, context?: Object): this
  	// Runs the given function `fn` when the map gets initialized with
  	// a view (center and zoom) and at least one layer, or immediately
  	// if it's already initialized, optionally passing a function context.
  	whenReady: function (callback, context) {
  		if (this._loaded) {
  			callback.call(context || this, {target: this});
  		} else {
  			this.on('load', callback, context);
  		}
  		return this;
  	},


  	// private methods for getting map state

  	_getMapPanePos: function () {
  		return getPosition(this._mapPane) || new Point(0, 0);
  	},

  	_moved: function () {
  		var pos = this._getMapPanePos();
  		return pos && !pos.equals([0, 0]);
  	},

  	_getTopLeftPoint: function (center, zoom) {
  		var pixelOrigin = center && zoom !== undefined ?
  			this._getNewPixelOrigin(center, zoom) :
  			this.getPixelOrigin();
  		return pixelOrigin.subtract(this._getMapPanePos());
  	},

  	_getNewPixelOrigin: function (center, zoom) {
  		var viewHalf = this.getSize()._divideBy(2);
  		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
  	},

  	_latLngToNewLayerPoint: function (latlng, zoom, center) {
  		var topLeft = this._getNewPixelOrigin(center, zoom);
  		return this.project(latlng, zoom)._subtract(topLeft);
  	},

  	_latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
  		var topLeft = this._getNewPixelOrigin(center, zoom);
  		return toBounds([
  			this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
  			this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
  			this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
  			this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
  		]);
  	},

  	// layer point of the current center
  	_getCenterLayerPoint: function () {
  		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
  	},

  	// offset of the specified place to the current center in pixels
  	_getCenterOffset: function (latlng) {
  		return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
  	},

  	// adjust center for view to get inside bounds
  	_limitCenter: function (center, zoom, bounds) {

  		if (!bounds) { return center; }

  		var centerPoint = this.project(center, zoom),
  		    viewHalf = this.getSize().divideBy(2),
  		    viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
  		    offset = this._getBoundsOffset(viewBounds, bounds, zoom);

  		// If offset is less than a pixel, ignore.
  		// This prevents unstable projections from getting into
  		// an infinite loop of tiny offsets.
  		if (offset.round().equals([0, 0])) {
  			return center;
  		}

  		return this.unproject(centerPoint.add(offset), zoom);
  	},

  	// adjust offset for view to get inside bounds
  	_limitOffset: function (offset, bounds) {
  		if (!bounds) { return offset; }

  		var viewBounds = this.getPixelBounds(),
  		    newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));

  		return offset.add(this._getBoundsOffset(newBounds, bounds));
  	},

  	// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
  	_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
  		var projectedMaxBounds = toBounds(
  		        this.project(maxBounds.getNorthEast(), zoom),
  		        this.project(maxBounds.getSouthWest(), zoom)
  		    ),
  		    minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
  		    maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),

  		    dx = this._rebound(minOffset.x, -maxOffset.x),
  		    dy = this._rebound(minOffset.y, -maxOffset.y);

  		return new Point(dx, dy);
  	},

  	_rebound: function (left, right) {
  		return left + right > 0 ?
  			Math.round(left - right) / 2 :
  			Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
  	},

  	_limitZoom: function (zoom) {
  		var min = this.getMinZoom(),
  		    max = this.getMaxZoom(),
  		    snap = Browser.any3d ? this.options.zoomSnap : 1;
  		if (snap) {
  			zoom = Math.round(zoom / snap) * snap;
  		}
  		return Math.max(min, Math.min(max, zoom));
  	},

  	_onPanTransitionStep: function () {
  		this.fire('move');
  	},

  	_onPanTransitionEnd: function () {
  		removeClass(this._mapPane, 'leaflet-pan-anim');
  		this.fire('moveend');
  	},

  	_tryAnimatedPan: function (center, options) {
  		// difference between the new and current centers in pixels
  		var offset = this._getCenterOffset(center)._trunc();

  		// don't animate too far unless animate: true specified in options
  		if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }

  		this.panBy(offset, options);

  		return true;
  	},

  	_createAnimProxy: function () {

  		var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
  		this._panes.mapPane.appendChild(proxy);

  		this.on('zoomanim', function (e) {
  			var prop = TRANSFORM,
  			    transform = this._proxy.style[prop];

  			setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));

  			// workaround for case when transform is the same and so transitionend event is not fired
  			if (transform === this._proxy.style[prop] && this._animatingZoom) {
  				this._onZoomTransitionEnd();
  			}
  		}, this);

  		this.on('load moveend', this._animMoveEnd, this);

  		this._on('unload', this._destroyAnimProxy, this);
  	},

  	_destroyAnimProxy: function () {
  		remove(this._proxy);
  		this.off('load moveend', this._animMoveEnd, this);
  		delete this._proxy;
  	},

  	_animMoveEnd: function () {
  		var c = this.getCenter(),
  		    z = this.getZoom();
  		setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
  	},

  	_catchTransitionEnd: function (e) {
  		if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
  			this._onZoomTransitionEnd();
  		}
  	},

  	_nothingToAnimate: function () {
  		return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
  	},

  	_tryAnimatedZoom: function (center, zoom, options) {

  		if (this._animatingZoom) { return true; }

  		options = options || {};

  		// don't animate if disabled, not supported or zoom difference is too large
  		if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
  		        Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }

  		// offset is the pixel coords of the zoom origin relative to the current center
  		var scale = this.getZoomScale(zoom),
  		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);

  		// don't animate if the zoom origin isn't within one screen from the current center, unless forced
  		if (options.animate !== true && !this.getSize().contains(offset)) { return false; }

  		requestAnimFrame(function () {
  			this
  			    ._moveStart(true, false)
  			    ._animateZoom(center, zoom, true);
  		}, this);

  		return true;
  	},

  	_animateZoom: function (center, zoom, startAnim, noUpdate) {
  		if (!this._mapPane) { return; }

  		if (startAnim) {
  			this._animatingZoom = true;

  			// remember what center/zoom to set after animation
  			this._animateToCenter = center;
  			this._animateToZoom = zoom;

  			addClass(this._mapPane, 'leaflet-zoom-anim');
  		}

  		// @section Other Events
  		// @event zoomanim: ZoomAnimEvent
  		// Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
  		this.fire('zoomanim', {
  			center: center,
  			zoom: zoom,
  			noUpdate: noUpdate
  		});

  		if (!this._tempFireZoomEvent) {
  			this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
  		}

  		this._move(this._animateToCenter, this._animateToZoom, undefined, true);

  		// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
  		setTimeout(bind(this._onZoomTransitionEnd, this), 250);
  	},

  	_onZoomTransitionEnd: function () {
  		if (!this._animatingZoom) { return; }

  		if (this._mapPane) {
  			removeClass(this._mapPane, 'leaflet-zoom-anim');
  		}

  		this._animatingZoom = false;

  		this._move(this._animateToCenter, this._animateToZoom, undefined, true);

  		if (this._tempFireZoomEvent) {
  			this.fire('zoom');
  		}
  		delete this._tempFireZoomEvent;

  		this.fire('move');

  		this._moveEnd(true);
  	}
  });

  // @section

  // @factory L.map(id: String, options?: Map options)
  // Instantiates a map object given the DOM ID of a `<div>` element
  // and optionally an object literal with `Map options`.
  //
  // @alternative
  // @factory L.map(el: HTMLElement, options?: Map options)
  // Instantiates a map object given an instance of a `<div>` HTML element
  // and optionally an object literal with `Map options`.
  function createMap(id, options) {
  	return new Map(id, options);
  }

  /*
   * @class Control
   * @aka L.Control
   * @inherits Class
   *
   * L.Control is a base class for implementing map controls. Handles positioning.
   * All other controls extend from this class.
   */

  var Control = Class.extend({
  	// @section
  	// @aka Control Options
  	options: {
  		// @option position: String = 'topright'
  		// The position of the control (one of the map corners). Possible values are `'topleft'`,
  		// `'topright'`, `'bottomleft'` or `'bottomright'`
  		position: 'topright'
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  	},

  	/* @section
  	 * Classes extending L.Control will inherit the following methods:
  	 *
  	 * @method getPosition: string
  	 * Returns the position of the control.
  	 */
  	getPosition: function () {
  		return this.options.position;
  	},

  	// @method setPosition(position: string): this
  	// Sets the position of the control.
  	setPosition: function (position) {
  		var map = this._map;

  		if (map) {
  			map.removeControl(this);
  		}

  		this.options.position = position;

  		if (map) {
  			map.addControl(this);
  		}

  		return this;
  	},

  	// @method getContainer: HTMLElement
  	// Returns the HTMLElement that contains the control.
  	getContainer: function () {
  		return this._container;
  	},

  	// @method addTo(map: Map): this
  	// Adds the control to the given map.
  	addTo: function (map) {
  		this.remove();
  		this._map = map;

  		var container = this._container = this.onAdd(map),
  		    pos = this.getPosition(),
  		    corner = map._controlCorners[pos];

  		addClass(container, 'leaflet-control');

  		if (pos.indexOf('bottom') !== -1) {
  			corner.insertBefore(container, corner.firstChild);
  		} else {
  			corner.appendChild(container);
  		}

  		this._map.on('unload', this.remove, this);

  		return this;
  	},

  	// @method remove: this
  	// Removes the control from the map it is currently active on.
  	remove: function () {
  		if (!this._map) {
  			return this;
  		}

  		remove(this._container);

  		if (this.onRemove) {
  			this.onRemove(this._map);
  		}

  		this._map.off('unload', this.remove, this);
  		this._map = null;

  		return this;
  	},

  	_refocusOnMap: function (e) {
  		// if map exists and event is not a keyboard event
  		if (this._map && e && e.screenX > 0 && e.screenY > 0) {
  			this._map.getContainer().focus();
  		}
  	}
  });

  var control = function (options) {
  	return new Control(options);
  };

  /* @section Extension methods
   * @uninheritable
   *
   * Every control should extend from `L.Control` and (re-)implement the following methods.
   *
   * @method onAdd(map: Map): HTMLElement
   * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
   *
   * @method onRemove(map: Map)
   * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
   */

  /* @namespace Map
   * @section Methods for Layers and Controls
   */
  Map.include({
  	// @method addControl(control: Control): this
  	// Adds the given control to the map
  	addControl: function (control) {
  		control.addTo(this);
  		return this;
  	},

  	// @method removeControl(control: Control): this
  	// Removes the given control from the map
  	removeControl: function (control) {
  		control.remove();
  		return this;
  	},

  	_initControlPos: function () {
  		var corners = this._controlCorners = {},
  		    l = 'leaflet-',
  		    container = this._controlContainer =
  		            create$1('div', l + 'control-container', this._container);

  		function createCorner(vSide, hSide) {
  			var className = l + vSide + ' ' + l + hSide;

  			corners[vSide + hSide] = create$1('div', className, container);
  		}

  		createCorner('top', 'left');
  		createCorner('top', 'right');
  		createCorner('bottom', 'left');
  		createCorner('bottom', 'right');
  	},

  	_clearControlPos: function () {
  		for (var i in this._controlCorners) {
  			remove(this._controlCorners[i]);
  		}
  		remove(this._controlContainer);
  		delete this._controlCorners;
  		delete this._controlContainer;
  	}
  });

  /*
   * @class Control.Layers
   * @aka L.Control.Layers
   * @inherits Control
   *
   * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.
   *
   * @example
   *
   * ```js
   * var baseLayers = {
   * 	"Mapbox": mapbox,
   * 	"OpenStreetMap": osm
   * };
   *
   * var overlays = {
   * 	"Marker": marker,
   * 	"Roads": roadsLayer
   * };
   *
   * L.control.layers(baseLayers, overlays).addTo(map);
   * ```
   *
   * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
   *
   * ```js
   * {
   *     "<someName1>": layer1,
   *     "<someName2>": layer2
   * }
   * ```
   *
   * The layer names can contain HTML, which allows you to add additional styling to the items:
   *
   * ```js
   * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
   * ```
   */

  var Layers = Control.extend({
  	// @section
  	// @aka Control.Layers options
  	options: {
  		// @option collapsed: Boolean = true
  		// If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
  		collapsed: true,
  		position: 'topright',

  		// @option autoZIndex: Boolean = true
  		// If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
  		autoZIndex: true,

  		// @option hideSingleBase: Boolean = false
  		// If `true`, the base layers in the control will be hidden when there is only one.
  		hideSingleBase: false,

  		// @option sortLayers: Boolean = false
  		// Whether to sort the layers. When `false`, layers will keep the order
  		// in which they were added to the control.
  		sortLayers: false,

  		// @option sortFunction: Function = *
  		// A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
  		// that will be used for sorting the layers, when `sortLayers` is `true`.
  		// The function receives both the `L.Layer` instances and their names, as in
  		// `sortFunction(layerA, layerB, nameA, nameB)`.
  		// By default, it sorts layers alphabetically by their name.
  		sortFunction: function (layerA, layerB, nameA, nameB) {
  			return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
  		}
  	},

  	initialize: function (baseLayers, overlays, options) {
  		setOptions(this, options);

  		this._layerControlInputs = [];
  		this._layers = [];
  		this._lastZIndex = 0;
  		this._handlingClick = false;

  		for (var i in baseLayers) {
  			this._addLayer(baseLayers[i], i);
  		}

  		for (i in overlays) {
  			this._addLayer(overlays[i], i, true);
  		}
  	},

  	onAdd: function (map) {
  		this._initLayout();
  		this._update();

  		this._map = map;
  		map.on('zoomend', this._checkDisabledLayers, this);

  		for (var i = 0; i < this._layers.length; i++) {
  			this._layers[i].layer.on('add remove', this._onLayerChange, this);
  		}

  		return this._container;
  	},

  	addTo: function (map) {
  		Control.prototype.addTo.call(this, map);
  		// Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
  		return this._expandIfNotCollapsed();
  	},

  	onRemove: function () {
  		this._map.off('zoomend', this._checkDisabledLayers, this);

  		for (var i = 0; i < this._layers.length; i++) {
  			this._layers[i].layer.off('add remove', this._onLayerChange, this);
  		}
  	},

  	// @method addBaseLayer(layer: Layer, name: String): this
  	// Adds a base layer (radio button entry) with the given name to the control.
  	addBaseLayer: function (layer, name) {
  		this._addLayer(layer, name);
  		return (this._map) ? this._update() : this;
  	},

  	// @method addOverlay(layer: Layer, name: String): this
  	// Adds an overlay (checkbox entry) with the given name to the control.
  	addOverlay: function (layer, name) {
  		this._addLayer(layer, name, true);
  		return (this._map) ? this._update() : this;
  	},

  	// @method removeLayer(layer: Layer): this
  	// Remove the given layer from the control.
  	removeLayer: function (layer) {
  		layer.off('add remove', this._onLayerChange, this);

  		var obj = this._getLayer(stamp(layer));
  		if (obj) {
  			this._layers.splice(this._layers.indexOf(obj), 1);
  		}
  		return (this._map) ? this._update() : this;
  	},

  	// @method expand(): this
  	// Expand the control container if collapsed.
  	expand: function () {
  		addClass(this._container, 'leaflet-control-layers-expanded');
  		this._section.style.height = null;
  		var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
  		if (acceptableHeight < this._section.clientHeight) {
  			addClass(this._section, 'leaflet-control-layers-scrollbar');
  			this._section.style.height = acceptableHeight + 'px';
  		} else {
  			removeClass(this._section, 'leaflet-control-layers-scrollbar');
  		}
  		this._checkDisabledLayers();
  		return this;
  	},

  	// @method collapse(): this
  	// Collapse the control container if expanded.
  	collapse: function () {
  		removeClass(this._container, 'leaflet-control-layers-expanded');
  		return this;
  	},

  	_initLayout: function () {
  		var className = 'leaflet-control-layers',
  		    container = this._container = create$1('div', className),
  		    collapsed = this.options.collapsed;

  		// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
  		container.setAttribute('aria-haspopup', true);

  		disableClickPropagation(container);
  		disableScrollPropagation(container);

  		var section = this._section = create$1('section', className + '-list');

  		if (collapsed) {
  			this._map.on('click', this.collapse, this);

  			on(container, {
  				mouseenter: function () {
  					on(section, 'click', preventDefault);
  					this.expand();
  					setTimeout(function () {
  						off(section, 'click', preventDefault);
  					});
  				},
  				mouseleave: this.collapse
  			}, this);
  		}

  		var link = this._layersLink = create$1('a', className + '-toggle', container);
  		link.href = '#';
  		link.title = 'Layers';
  		link.setAttribute('role', 'button');

  		on(link, 'click', preventDefault); // prevent link function
  		on(link, 'focus', this.expand, this);

  		if (!collapsed) {
  			this.expand();
  		}

  		this._baseLayersList = create$1('div', className + '-base', section);
  		this._separator = create$1('div', className + '-separator', section);
  		this._overlaysList = create$1('div', className + '-overlays', section);

  		container.appendChild(section);
  	},

  	_getLayer: function (id) {
  		for (var i = 0; i < this._layers.length; i++) {

  			if (this._layers[i] && stamp(this._layers[i].layer) === id) {
  				return this._layers[i];
  			}
  		}
  	},

  	_addLayer: function (layer, name, overlay) {
  		if (this._map) {
  			layer.on('add remove', this._onLayerChange, this);
  		}

  		this._layers.push({
  			layer: layer,
  			name: name,
  			overlay: overlay
  		});

  		if (this.options.sortLayers) {
  			this._layers.sort(bind(function (a, b) {
  				return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
  			}, this));
  		}

  		if (this.options.autoZIndex && layer.setZIndex) {
  			this._lastZIndex++;
  			layer.setZIndex(this._lastZIndex);
  		}

  		this._expandIfNotCollapsed();
  	},

  	_update: function () {
  		if (!this._container) { return this; }

  		empty(this._baseLayersList);
  		empty(this._overlaysList);

  		this._layerControlInputs = [];
  		var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;

  		for (i = 0; i < this._layers.length; i++) {
  			obj = this._layers[i];
  			this._addItem(obj);
  			overlaysPresent = overlaysPresent || obj.overlay;
  			baseLayersPresent = baseLayersPresent || !obj.overlay;
  			baseLayersCount += !obj.overlay ? 1 : 0;
  		}

  		// Hide base layers section if there's only one layer.
  		if (this.options.hideSingleBase) {
  			baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
  			this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
  		}

  		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';

  		return this;
  	},

  	_onLayerChange: function (e) {
  		if (!this._handlingClick) {
  			this._update();
  		}

  		var obj = this._getLayer(stamp(e.target));

  		// @namespace Map
  		// @section Layer events
  		// @event baselayerchange: LayersControlEvent
  		// Fired when the base layer is changed through the [layers control](#control-layers).
  		// @event overlayadd: LayersControlEvent
  		// Fired when an overlay is selected through the [layers control](#control-layers).
  		// @event overlayremove: LayersControlEvent
  		// Fired when an overlay is deselected through the [layers control](#control-layers).
  		// @namespace Control.Layers
  		var type = obj.overlay ?
  			(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
  			(e.type === 'add' ? 'baselayerchange' : null);

  		if (type) {
  			this._map.fire(type, obj);
  		}
  	},

  	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
  	_createRadioElement: function (name, checked) {

  		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
  				name + '"' + (checked ? ' checked="checked"' : '') + '/>';

  		var radioFragment = document.createElement('div');
  		radioFragment.innerHTML = radioHtml;

  		return radioFragment.firstChild;
  	},

  	_addItem: function (obj) {
  		var label = document.createElement('label'),
  		    checked = this._map.hasLayer(obj.layer),
  		    input;

  		if (obj.overlay) {
  			input = document.createElement('input');
  			input.type = 'checkbox';
  			input.className = 'leaflet-control-layers-selector';
  			input.defaultChecked = checked;
  		} else {
  			input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
  		}

  		this._layerControlInputs.push(input);
  		input.layerId = stamp(obj.layer);

  		on(input, 'click', this._onInputClick, this);

  		var name = document.createElement('span');
  		name.innerHTML = ' ' + obj.name;

  		// Helps from preventing layer control flicker when checkboxes are disabled
  		// https://github.com/Leaflet/Leaflet/issues/2771
  		var holder = document.createElement('span');

  		label.appendChild(holder);
  		holder.appendChild(input);
  		holder.appendChild(name);

  		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
  		container.appendChild(label);

  		this._checkDisabledLayers();
  		return label;
  	},

  	_onInputClick: function () {
  		var inputs = this._layerControlInputs,
  		    input, layer;
  		var addedLayers = [],
  		    removedLayers = [];

  		this._handlingClick = true;

  		for (var i = inputs.length - 1; i >= 0; i--) {
  			input = inputs[i];
  			layer = this._getLayer(input.layerId).layer;

  			if (input.checked) {
  				addedLayers.push(layer);
  			} else if (!input.checked) {
  				removedLayers.push(layer);
  			}
  		}

  		// Bugfix issue 2318: Should remove all old layers before readding new ones
  		for (i = 0; i < removedLayers.length; i++) {
  			if (this._map.hasLayer(removedLayers[i])) {
  				this._map.removeLayer(removedLayers[i]);
  			}
  		}
  		for (i = 0; i < addedLayers.length; i++) {
  			if (!this._map.hasLayer(addedLayers[i])) {
  				this._map.addLayer(addedLayers[i]);
  			}
  		}

  		this._handlingClick = false;

  		this._refocusOnMap();
  	},

  	_checkDisabledLayers: function () {
  		var inputs = this._layerControlInputs,
  		    input,
  		    layer,
  		    zoom = this._map.getZoom();

  		for (var i = inputs.length - 1; i >= 0; i--) {
  			input = inputs[i];
  			layer = this._getLayer(input.layerId).layer;
  			input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
  			                 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);

  		}
  	},

  	_expandIfNotCollapsed: function () {
  		if (this._map && !this.options.collapsed) {
  			this.expand();
  		}
  		return this;
  	}

  });


  // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
  // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
  var layers = function (baseLayers, overlays, options) {
  	return new Layers(baseLayers, overlays, options);
  };

  /*
   * @class Control.Zoom
   * @aka L.Control.Zoom
   * @inherits Control
   *
   * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
   */

  var Zoom = Control.extend({
  	// @section
  	// @aka Control.Zoom options
  	options: {
  		position: 'topleft',

  		// @option zoomInText: String = '<span aria-hidden="true">+</span>'
  		// The text set on the 'zoom in' button.
  		zoomInText: '<span aria-hidden="true">+</span>',

  		// @option zoomInTitle: String = 'Zoom in'
  		// The title set on the 'zoom in' button.
  		zoomInTitle: 'Zoom in',

  		// @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
  		// The text set on the 'zoom out' button.
  		zoomOutText: '<span aria-hidden="true">&#x2212;</span>',

  		// @option zoomOutTitle: String = 'Zoom out'
  		// The title set on the 'zoom out' button.
  		zoomOutTitle: 'Zoom out'
  	},

  	onAdd: function (map) {
  		var zoomName = 'leaflet-control-zoom',
  		    container = create$1('div', zoomName + ' leaflet-bar'),
  		    options = this.options;

  		this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
  		        zoomName + '-in',  container, this._zoomIn);
  		this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
  		        zoomName + '-out', container, this._zoomOut);

  		this._updateDisabled();
  		map.on('zoomend zoomlevelschange', this._updateDisabled, this);

  		return container;
  	},

  	onRemove: function (map) {
  		map.off('zoomend zoomlevelschange', this._updateDisabled, this);
  	},

  	disable: function () {
  		this._disabled = true;
  		this._updateDisabled();
  		return this;
  	},

  	enable: function () {
  		this._disabled = false;
  		this._updateDisabled();
  		return this;
  	},

  	_zoomIn: function (e) {
  		if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
  			this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
  		}
  	},

  	_zoomOut: function (e) {
  		if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
  			this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
  		}
  	},

  	_createButton: function (html, title, className, container, fn) {
  		var link = create$1('a', className, container);
  		link.innerHTML = html;
  		link.href = '#';
  		link.title = title;

  		/*
  		 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
  		 */
  		link.setAttribute('role', 'button');
  		link.setAttribute('aria-label', title);

  		disableClickPropagation(link);
  		on(link, 'click', stop);
  		on(link, 'click', fn, this);
  		on(link, 'click', this._refocusOnMap, this);

  		return link;
  	},

  	_updateDisabled: function () {
  		var map = this._map,
  		    className = 'leaflet-disabled';

  		removeClass(this._zoomInButton, className);
  		removeClass(this._zoomOutButton, className);
  		this._zoomInButton.setAttribute('aria-disabled', 'false');
  		this._zoomOutButton.setAttribute('aria-disabled', 'false');

  		if (this._disabled || map._zoom === map.getMinZoom()) {
  			addClass(this._zoomOutButton, className);
  			this._zoomOutButton.setAttribute('aria-disabled', 'true');
  		}
  		if (this._disabled || map._zoom === map.getMaxZoom()) {
  			addClass(this._zoomInButton, className);
  			this._zoomInButton.setAttribute('aria-disabled', 'true');
  		}
  	}
  });

  // @namespace Map
  // @section Control options
  // @option zoomControl: Boolean = true
  // Whether a [zoom control](#control-zoom) is added to the map by default.
  Map.mergeOptions({
  	zoomControl: true
  });

  Map.addInitHook(function () {
  	if (this.options.zoomControl) {
  		// @section Controls
  		// @property zoomControl: Control.Zoom
  		// The default zoom control (only available if the
  		// [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
  		this.zoomControl = new Zoom();
  		this.addControl(this.zoomControl);
  	}
  });

  // @namespace Control.Zoom
  // @factory L.control.zoom(options: Control.Zoom options)
  // Creates a zoom control
  var zoom = function (options) {
  	return new Zoom(options);
  };

  /*
   * @class Control.Scale
   * @aka L.Control.Scale
   * @inherits Control
   *
   * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
   *
   * @example
   *
   * ```js
   * L.control.scale().addTo(map);
   * ```
   */

  var Scale = Control.extend({
  	// @section
  	// @aka Control.Scale options
  	options: {
  		position: 'bottomleft',

  		// @option maxWidth: Number = 100
  		// Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
  		maxWidth: 100,

  		// @option metric: Boolean = True
  		// Whether to show the metric scale line (m/km).
  		metric: true,

  		// @option imperial: Boolean = True
  		// Whether to show the imperial scale line (mi/ft).
  		imperial: true

  		// @option updateWhenIdle: Boolean = false
  		// If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
  	},

  	onAdd: function (map) {
  		var className = 'leaflet-control-scale',
  		    container = create$1('div', className),
  		    options = this.options;

  		this._addScales(options, className + '-line', container);

  		map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
  		map.whenReady(this._update, this);

  		return container;
  	},

  	onRemove: function (map) {
  		map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
  	},

  	_addScales: function (options, className, container) {
  		if (options.metric) {
  			this._mScale = create$1('div', className, container);
  		}
  		if (options.imperial) {
  			this._iScale = create$1('div', className, container);
  		}
  	},

  	_update: function () {
  		var map = this._map,
  		    y = map.getSize().y / 2;

  		var maxMeters = map.distance(
  			map.containerPointToLatLng([0, y]),
  			map.containerPointToLatLng([this.options.maxWidth, y]));

  		this._updateScales(maxMeters);
  	},

  	_updateScales: function (maxMeters) {
  		if (this.options.metric && maxMeters) {
  			this._updateMetric(maxMeters);
  		}
  		if (this.options.imperial && maxMeters) {
  			this._updateImperial(maxMeters);
  		}
  	},

  	_updateMetric: function (maxMeters) {
  		var meters = this._getRoundNum(maxMeters),
  		    label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';

  		this._updateScale(this._mScale, label, meters / maxMeters);
  	},

  	_updateImperial: function (maxMeters) {
  		var maxFeet = maxMeters * 3.2808399,
  		    maxMiles, miles, feet;

  		if (maxFeet > 5280) {
  			maxMiles = maxFeet / 5280;
  			miles = this._getRoundNum(maxMiles);
  			this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);

  		} else {
  			feet = this._getRoundNum(maxFeet);
  			this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
  		}
  	},

  	_updateScale: function (scale, text, ratio) {
  		scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
  		scale.innerHTML = text;
  	},

  	_getRoundNum: function (num) {
  		var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
  		    d = num / pow10;

  		d = d >= 10 ? 10 :
  		    d >= 5 ? 5 :
  		    d >= 3 ? 3 :
  		    d >= 2 ? 2 : 1;

  		return pow10 * d;
  	}
  });


  // @factory L.control.scale(options?: Control.Scale options)
  // Creates an scale control with the given options.
  var scale = function (options) {
  	return new Scale(options);
  };

  var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';


  /*
   * @class Control.Attribution
   * @aka L.Control.Attribution
   * @inherits Control
   *
   * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
   */

  var Attribution = Control.extend({
  	// @section
  	// @aka Control.Attribution options
  	options: {
  		position: 'bottomright',

  		// @option prefix: String|false = 'Leaflet'
  		// The HTML text shown before the attributions. Pass `false` to disable.
  		prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
  	},

  	initialize: function (options) {
  		setOptions(this, options);

  		this._attributions = {};
  	},

  	onAdd: function (map) {
  		map.attributionControl = this;
  		this._container = create$1('div', 'leaflet-control-attribution');
  		disableClickPropagation(this._container);

  		// TODO ugly, refactor
  		for (var i in map._layers) {
  			if (map._layers[i].getAttribution) {
  				this.addAttribution(map._layers[i].getAttribution());
  			}
  		}

  		this._update();

  		map.on('layeradd', this._addAttribution, this);

  		return this._container;
  	},

  	onRemove: function (map) {
  		map.off('layeradd', this._addAttribution, this);
  	},

  	_addAttribution: function (ev) {
  		if (ev.layer.getAttribution) {
  			this.addAttribution(ev.layer.getAttribution());
  			ev.layer.once('remove', function () {
  				this.removeAttribution(ev.layer.getAttribution());
  			}, this);
  		}
  	},

  	// @method setPrefix(prefix: String|false): this
  	// The HTML text shown before the attributions. Pass `false` to disable.
  	setPrefix: function (prefix) {
  		this.options.prefix = prefix;
  		this._update();
  		return this;
  	},

  	// @method addAttribution(text: String): this
  	// Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
  	addAttribution: function (text) {
  		if (!text) { return this; }

  		if (!this._attributions[text]) {
  			this._attributions[text] = 0;
  		}
  		this._attributions[text]++;

  		this._update();

  		return this;
  	},

  	// @method removeAttribution(text: String): this
  	// Removes an attribution text.
  	removeAttribution: function (text) {
  		if (!text) { return this; }

  		if (this._attributions[text]) {
  			this._attributions[text]--;
  			this._update();
  		}

  		return this;
  	},

  	_update: function () {
  		if (!this._map) { return; }

  		var attribs = [];

  		for (var i in this._attributions) {
  			if (this._attributions[i]) {
  				attribs.push(i);
  			}
  		}

  		var prefixAndAttribs = [];

  		if (this.options.prefix) {
  			prefixAndAttribs.push(this.options.prefix);
  		}
  		if (attribs.length) {
  			prefixAndAttribs.push(attribs.join(', '));
  		}

  		this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
  	}
  });

  // @namespace Map
  // @section Control options
  // @option attributionControl: Boolean = true
  // Whether a [attribution control](#control-attribution) is added to the map by default.
  Map.mergeOptions({
  	attributionControl: true
  });

  Map.addInitHook(function () {
  	if (this.options.attributionControl) {
  		new Attribution().addTo(this);
  	}
  });

  // @namespace Control.Attribution
  // @factory L.control.attribution(options: Control.Attribution options)
  // Creates an attribution control.
  var attribution = function (options) {
  	return new Attribution(options);
  };

  Control.Layers = Layers;
  Control.Zoom = Zoom;
  Control.Scale = Scale;
  Control.Attribution = Attribution;

  control.layers = layers;
  control.zoom = zoom;
  control.scale = scale;
  control.attribution = attribution;

  /*
  	L.Handler is a base class for handler classes that are used internally to inject
  	interaction features like dragging to classes like Map and Marker.
  */

  // @class Handler
  // @aka L.Handler
  // Abstract class for map interaction handlers

  var Handler = Class.extend({
  	initialize: function (map) {
  		this._map = map;
  	},

  	// @method enable(): this
  	// Enables the handler
  	enable: function () {
  		if (this._enabled) { return this; }

  		this._enabled = true;
  		this.addHooks();
  		return this;
  	},

  	// @method disable(): this
  	// Disables the handler
  	disable: function () {
  		if (!this._enabled) { return this; }

  		this._enabled = false;
  		this.removeHooks();
  		return this;
  	},

  	// @method enabled(): Boolean
  	// Returns `true` if the handler is enabled
  	enabled: function () {
  		return !!this._enabled;
  	}

  	// @section Extension methods
  	// Classes inheriting from `Handler` must implement the two following methods:
  	// @method addHooks()
  	// Called when the handler is enabled, should add event hooks.
  	// @method removeHooks()
  	// Called when the handler is disabled, should remove the event hooks added previously.
  });

  // @section There is static function which can be called without instantiating L.Handler:
  // @function addTo(map: Map, name: String): this
  // Adds a new Handler to the given map with the given name.
  Handler.addTo = function (map, name) {
  	map.addHandler(name, this);
  	return this;
  };

  var Mixin = {Events: Events};

  /*
   * @class Draggable
   * @aka L.Draggable
   * @inherits Evented
   *
   * A class for making DOM elements draggable (including touch support).
   * Used internally for map and marker dragging. Only works for elements
   * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
   *
   * @example
   * ```js
   * var draggable = new L.Draggable(elementToDrag);
   * draggable.enable();
   * ```
   */

  var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';

  var Draggable = Evented.extend({

  	options: {
  		// @section
  		// @aka Draggable options
  		// @option clickTolerance: Number = 3
  		// The max number of pixels a user can shift the mouse pointer during a click
  		// for it to be considered a valid click (as opposed to a mouse drag).
  		clickTolerance: 3
  	},

  	// @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
  	// Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
  	initialize: function (element, dragStartTarget, preventOutline, options) {
  		setOptions(this, options);

  		this._element = element;
  		this._dragStartTarget = dragStartTarget || element;
  		this._preventOutline = preventOutline;
  	},

  	// @method enable()
  	// Enables the dragging ability
  	enable: function () {
  		if (this._enabled) { return; }

  		on(this._dragStartTarget, START, this._onDown, this);

  		this._enabled = true;
  	},

  	// @method disable()
  	// Disables the dragging ability
  	disable: function () {
  		if (!this._enabled) { return; }

  		// If we're currently dragging this draggable,
  		// disabling it counts as first ending the drag.
  		if (Draggable._dragging === this) {
  			this.finishDrag(true);
  		}

  		off(this._dragStartTarget, START, this._onDown, this);

  		this._enabled = false;
  		this._moved = false;
  	},

  	_onDown: function (e) {
  		// Ignore the event if disabled; this happens in IE11
  		// under some circumstances, see #3666.
  		if (!this._enabled) { return; }

  		this._moved = false;

  		if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }

  		if (e.touches && e.touches.length !== 1) {
  			// Finish dragging to avoid conflict with touchZoom
  			if (Draggable._dragging === this) {
  				this.finishDrag();
  			}
  			return;
  		}

  		if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
  		Draggable._dragging = this;  // Prevent dragging multiple objects at once.

  		if (this._preventOutline) {
  			preventOutline(this._element);
  		}

  		disableImageDrag();
  		disableTextSelection();

  		if (this._moving) { return; }

  		// @event down: Event
  		// Fired when a drag is about to start.
  		this.fire('down');

  		var first = e.touches ? e.touches[0] : e,
  		    sizedParent = getSizedParentNode(this._element);

  		this._startPoint = new Point(first.clientX, first.clientY);
  		this._startPos = getPosition(this._element);

  		// Cache the scale, so that we can continuously compensate for it during drag (_onMove).
  		this._parentScale = getScale(sizedParent);

  		var mouseevent = e.type === 'mousedown';
  		on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
  		on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
  	},

  	_onMove: function (e) {
  		// Ignore the event if disabled; this happens in IE11
  		// under some circumstances, see #3666.
  		if (!this._enabled) { return; }

  		if (e.touches && e.touches.length > 1) {
  			this._moved = true;
  			return;
  		}

  		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
  		    offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);

  		if (!offset.x && !offset.y) { return; }
  		if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }

  		// We assume that the parent container's position, border and scale do not change for the duration of the drag.
  		// Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
  		// and we can use the cached value for the scale.
  		offset.x /= this._parentScale.x;
  		offset.y /= this._parentScale.y;

  		preventDefault(e);

  		if (!this._moved) {
  			// @event dragstart: Event
  			// Fired when a drag starts
  			this.fire('dragstart');

  			this._moved = true;

  			addClass(document.body, 'leaflet-dragging');

  			this._lastTarget = e.target || e.srcElement;
  			// IE and Edge do not give the <use> element, so fetch it
  			// if necessary
  			if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
  				this._lastTarget = this._lastTarget.correspondingUseElement;
  			}
  			addClass(this._lastTarget, 'leaflet-drag-target');
  		}

  		this._newPos = this._startPos.add(offset);
  		this._moving = true;

  		this._lastEvent = e;
  		this._updatePosition();
  	},

  	_updatePosition: function () {
  		var e = {originalEvent: this._lastEvent};

  		// @event predrag: Event
  		// Fired continuously during dragging *before* each corresponding
  		// update of the element's position.
  		this.fire('predrag', e);
  		setPosition(this._element, this._newPos);

  		// @event drag: Event
  		// Fired continuously during dragging.
  		this.fire('drag', e);
  	},

  	_onUp: function () {
  		// Ignore the event if disabled; this happens in IE11
  		// under some circumstances, see #3666.
  		if (!this._enabled) { return; }
  		this.finishDrag();
  	},

  	finishDrag: function (noInertia) {
  		removeClass(document.body, 'leaflet-dragging');

  		if (this._lastTarget) {
  			removeClass(this._lastTarget, 'leaflet-drag-target');
  			this._lastTarget = null;
  		}

  		off(document, 'mousemove touchmove', this._onMove, this);
  		off(document, 'mouseup touchend touchcancel', this._onUp, this);

  		enableImageDrag();
  		enableTextSelection();

  		if (this._moved && this._moving) {

  			// @event dragend: DragEndEvent
  			// Fired when the drag ends.
  			this.fire('dragend', {
  				noInertia: noInertia,
  				distance: this._newPos.distanceTo(this._startPos)
  			});
  		}

  		this._moving = false;
  		Draggable._dragging = false;
  	}

  });

  /*
   * @namespace LineUtil
   *
   * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
   */

  // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
  // Improves rendering performance dramatically by lessening the number of points to draw.

  // @function simplify(points: Point[], tolerance: Number): Point[]
  // Dramatically reduces the number of points in a polyline while retaining
  // its shape and returns a new array of simplified points, using the
  // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
  // Used for a huge performance boost when processing/displaying Leaflet polylines for
  // each zoom level and also reducing visual noise. tolerance affects the amount of
  // simplification (lesser value means higher quality but slower and with more points).
  // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
  function simplify(points, tolerance) {
  	if (!tolerance || !points.length) {
  		return points.slice();
  	}

  	var sqTolerance = tolerance * tolerance;

  	    // stage 1: vertex reduction
  	    points = _reducePoints(points, sqTolerance);

  	    // stage 2: Douglas-Peucker simplification
  	    points = _simplifyDP(points, sqTolerance);

  	return points;
  }

  // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
  // Returns the distance between point `p` and segment `p1` to `p2`.
  function pointToSegmentDistance(p, p1, p2) {
  	return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
  }

  // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
  // Returns the closest point from a point `p` on a segment `p1` to `p2`.
  function closestPointOnSegment(p, p1, p2) {
  	return _sqClosestPointOnSegment(p, p1, p2);
  }

  // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
  function _simplifyDP(points, sqTolerance) {

  	var len = points.length,
  	    ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
  	    markers = new ArrayConstructor(len);

  	    markers[0] = markers[len - 1] = 1;

  	_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);

  	var i,
  	    newPoints = [];

  	for (i = 0; i < len; i++) {
  		if (markers[i]) {
  			newPoints.push(points[i]);
  		}
  	}

  	return newPoints;
  }

  function _simplifyDPStep(points, markers, sqTolerance, first, last) {

  	var maxSqDist = 0,
  	index, i, sqDist;

  	for (i = first + 1; i <= last - 1; i++) {
  		sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);

  		if (sqDist > maxSqDist) {
  			index = i;
  			maxSqDist = sqDist;
  		}
  	}

  	if (maxSqDist > sqTolerance) {
  		markers[index] = 1;

  		_simplifyDPStep(points, markers, sqTolerance, first, index);
  		_simplifyDPStep(points, markers, sqTolerance, index, last);
  	}
  }

  // reduce points that are too close to each other to a single point
  function _reducePoints(points, sqTolerance) {
  	var reducedPoints = [points[0]];

  	for (var i = 1, prev = 0, len = points.length; i < len; i++) {
  		if (_sqDist(points[i], points[prev]) > sqTolerance) {
  			reducedPoints.push(points[i]);
  			prev = i;
  		}
  	}
  	if (prev < len - 1) {
  		reducedPoints.push(points[len - 1]);
  	}
  	return reducedPoints;
  }

  var _lastCode;

  // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
  // Clips the segment a to b by rectangular bounds with the
  // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
  // (modifying the segment points directly!). Used by Leaflet to only show polyline
  // points that are on the screen or near, increasing performance.
  function clipSegment(a, b, bounds, useLastCode, round) {
  	var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
  	    codeB = _getBitCode(b, bounds),

  	    codeOut, p, newCode;

  	    // save 2nd code to avoid calculating it on the next segment
  	    _lastCode = codeB;

  	while (true) {
  		// if a,b is inside the clip window (trivial accept)
  		if (!(codeA | codeB)) {
  			return [a, b];
  		}

  		// if a,b is outside the clip window (trivial reject)
  		if (codeA & codeB) {
  			return false;
  		}

  		// other cases
  		codeOut = codeA || codeB;
  		p = _getEdgeIntersection(a, b, codeOut, bounds, round);
  		newCode = _getBitCode(p, bounds);

  		if (codeOut === codeA) {
  			a = p;
  			codeA = newCode;
  		} else {
  			b = p;
  			codeB = newCode;
  		}
  	}
  }

  function _getEdgeIntersection(a, b, code, bounds, round) {
  	var dx = b.x - a.x,
  	    dy = b.y - a.y,
  	    min = bounds.min,
  	    max = bounds.max,
  	    x, y;

  	if (code & 8) { // top
  		x = a.x + dx * (max.y - a.y) / dy;
  		y = max.y;

  	} else if (code & 4) { // bottom
  		x = a.x + dx * (min.y - a.y) / dy;
  		y = min.y;

  	} else if (code & 2) { // right
  		x = max.x;
  		y = a.y + dy * (max.x - a.x) / dx;

  	} else if (code & 1) { // left
  		x = min.x;
  		y = a.y + dy * (min.x - a.x) / dx;
  	}

  	return new Point(x, y, round);
  }

  function _getBitCode(p, bounds) {
  	var code = 0;

  	if (p.x < bounds.min.x) { // left
  		code |= 1;
  	} else if (p.x > bounds.max.x) { // right
  		code |= 2;
  	}

  	if (p.y < bounds.min.y) { // bottom
  		code |= 4;
  	} else if (p.y > bounds.max.y) { // top
  		code |= 8;
  	}

  	return code;
  }

  // square distance (to avoid unnecessary Math.sqrt calls)
  function _sqDist(p1, p2) {
  	var dx = p2.x - p1.x,
  	    dy = p2.y - p1.y;
  	return dx * dx + dy * dy;
  }

  // return closest point on segment or distance to that point
  function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
  	var x = p1.x,
  	    y = p1.y,
  	    dx = p2.x - x,
  	    dy = p2.y - y,
  	    dot = dx * dx + dy * dy,
  	    t;

  	if (dot > 0) {
  		t = ((p.x - x) * dx + (p.y - y) * dy) / dot;

  		if (t > 1) {
  			x = p2.x;
  			y = p2.y;
  		} else if (t > 0) {
  			x += dx * t;
  			y += dy * t;
  		}
  	}

  	dx = p.x - x;
  	dy = p.y - y;

  	return sqDist ? dx * dx + dy * dy : new Point(x, y);
  }


  // @function isFlat(latlngs: LatLng[]): Boolean
  // Returns true if `latlngs` is a flat array, false is nested.
  function isFlat(latlngs) {
  	return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
  }

  function _flat(latlngs) {
  	console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
  	return isFlat(latlngs);
  }

  var LineUtil = {
    __proto__: null,
    simplify: simplify,
    pointToSegmentDistance: pointToSegmentDistance,
    closestPointOnSegment: closestPointOnSegment,
    clipSegment: clipSegment,
    _getEdgeIntersection: _getEdgeIntersection,
    _getBitCode: _getBitCode,
    _sqClosestPointOnSegment: _sqClosestPointOnSegment,
    isFlat: isFlat,
    _flat: _flat
  };

  /*
   * @namespace PolyUtil
   * Various utility functions for polygon geometries.
   */

  /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
   * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
   * Used by Leaflet to only show polygon points that are on the screen or near, increasing
   * performance. Note that polygon points needs different algorithm for clipping
   * than polyline, so there's a separate method for it.
   */
  function clipPolygon(points, bounds, round) {
  	var clippedPoints,
  	    edges = [1, 4, 2, 8],
  	    i, j, k,
  	    a, b,
  	    len, edge, p;

  	for (i = 0, len = points.length; i < len; i++) {
  		points[i]._code = _getBitCode(points[i], bounds);
  	}

  	// for each edge (left, bottom, right, top)
  	for (k = 0; k < 4; k++) {
  		edge = edges[k];
  		clippedPoints = [];

  		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
  			a = points[i];
  			b = points[j];

  			// if a is inside the clip window
  			if (!(a._code & edge)) {
  				// if b is outside the clip window (a->b goes out of screen)
  				if (b._code & edge) {
  					p = _getEdgeIntersection(b, a, edge, bounds, round);
  					p._code = _getBitCode(p, bounds);
  					clippedPoints.push(p);
  				}
  				clippedPoints.push(a);

  			// else if b is inside the clip window (a->b enters the screen)
  			} else if (!(b._code & edge)) {
  				p = _getEdgeIntersection(b, a, edge, bounds, round);
  				p._code = _getBitCode(p, bounds);
  				clippedPoints.push(p);
  			}
  		}
  		points = clippedPoints;
  	}

  	return points;
  }

  var PolyUtil = {
    __proto__: null,
    clipPolygon: clipPolygon
  };

  /*
   * @namespace Projection
   * @section
   * Leaflet comes with a set of already defined Projections out of the box:
   *
   * @projection L.Projection.LonLat
   *
   * Equirectangular, or Plate Carree projection — the most simple projection,
   * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
   * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
   * `EPSG:4326` and `Simple` CRS.
   */

  var LonLat = {
  	project: function (latlng) {
  		return new Point(latlng.lng, latlng.lat);
  	},

  	unproject: function (point) {
  		return new LatLng(point.y, point.x);
  	},

  	bounds: new Bounds([-180, -90], [180, 90])
  };

  /*
   * @namespace Projection
   * @projection L.Projection.Mercator
   *
   * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
   */

  var Mercator = {
  	R: 6378137,
  	R_MINOR: 6356752.314245179,

  	bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),

  	project: function (latlng) {
  		var d = Math.PI / 180,
  		    r = this.R,
  		    y = latlng.lat * d,
  		    tmp = this.R_MINOR / r,
  		    e = Math.sqrt(1 - tmp * tmp),
  		    con = e * Math.sin(y);

  		var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
  		y = -r * Math.log(Math.max(ts, 1E-10));

  		return new Point(latlng.lng * d * r, y);
  	},

  	unproject: function (point) {
  		var d = 180 / Math.PI,
  		    r = this.R,
  		    tmp = this.R_MINOR / r,
  		    e = Math.sqrt(1 - tmp * tmp),
  		    ts = Math.exp(-point.y / r),
  		    phi = Math.PI / 2 - 2 * Math.atan(ts);

  		for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
  			con = e * Math.sin(phi);
  			con = Math.pow((1 - con) / (1 + con), e / 2);
  			dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
  			phi += dphi;
  		}

  		return new LatLng(phi * d, point.x * d / r);
  	}
  };

  /*
   * @class Projection

   * An object with methods for projecting geographical coordinates of the world onto
   * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).

   * @property bounds: Bounds
   * The bounds (specified in CRS units) where the projection is valid

   * @method project(latlng: LatLng): Point
   * Projects geographical coordinates into a 2D point.
   * Only accepts actual `L.LatLng` instances, not arrays.

   * @method unproject(point: Point): LatLng
   * The inverse of `project`. Projects a 2D point into a geographical location.
   * Only accepts actual `L.Point` instances, not arrays.

   * Note that the projection instances do not inherit from Leaflet's `Class` object,
   * and can't be instantiated. Also, new classes can't inherit from them,
   * and methods can't be added to them with the `include` function.

   */

  var index = {
    __proto__: null,
    LonLat: LonLat,
    Mercator: Mercator,
    SphericalMercator: SphericalMercator
  };

  /*
   * @namespace CRS
   * @crs L.CRS.EPSG3395
   *
   * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
   */
  var EPSG3395 = extend({}, Earth, {
  	code: 'EPSG:3395',
  	projection: Mercator,

  	transformation: (function () {
  		var scale = 0.5 / (Math.PI * Mercator.R);
  		return toTransformation(scale, 0.5, -scale, 0.5);
  	}())
  });

  /*
   * @namespace CRS
   * @crs L.CRS.EPSG4326
   *
   * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
   *
   * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
   * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
   * with this CRS, ensure that there are two 256x256 pixel tiles covering the
   * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
   * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
   */

  var EPSG4326 = extend({}, Earth, {
  	code: 'EPSG:4326',
  	projection: LonLat,
  	transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
  });

  /*
   * @namespace CRS
   * @crs L.CRS.Simple
   *
   * A simple CRS that maps longitude and latitude into `x` and `y` directly.
   * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
   * axis should still be inverted (going from bottom to top). `distance()` returns
   * simple euclidean distance.
   */

  var Simple = extend({}, CRS, {
  	projection: LonLat,
  	transformation: toTransformation(1, 0, -1, 0),

  	scale: function (zoom) {
  		return Math.pow(2, zoom);
  	},

  	zoom: function (scale) {
  		return Math.log(scale) / Math.LN2;
  	},

  	distance: function (latlng1, latlng2) {
  		var dx = latlng2.lng - latlng1.lng,
  		    dy = latlng2.lat - latlng1.lat;

  		return Math.sqrt(dx * dx + dy * dy);
  	},

  	infinite: true
  });

  CRS.Earth = Earth;
  CRS.EPSG3395 = EPSG3395;
  CRS.EPSG3857 = EPSG3857;
  CRS.EPSG900913 = EPSG900913;
  CRS.EPSG4326 = EPSG4326;
  CRS.Simple = Simple;

  /*
   * @class Layer
   * @inherits Evented
   * @aka L.Layer
   * @aka ILayer
   *
   * A set of methods from the Layer base class that all Leaflet layers use.
   * Inherits all methods, options and events from `L.Evented`.
   *
   * @example
   *
   * ```js
   * var layer = L.marker(latlng).addTo(map);
   * layer.addTo(map);
   * layer.remove();
   * ```
   *
   * @event add: Event
   * Fired after the layer is added to a map
   *
   * @event remove: Event
   * Fired after the layer is removed from a map
   */


  var Layer = Evented.extend({

  	// Classes extending `L.Layer` will inherit the following options:
  	options: {
  		// @option pane: String = 'overlayPane'
  		// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
  		pane: 'overlayPane',

  		// @option attribution: String = null
  		// String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
  		attribution: null,

  		bubblingMouseEvents: true
  	},

  	/* @section
  	 * Classes extending `L.Layer` will inherit the following methods:
  	 *
  	 * @method addTo(map: Map|LayerGroup): this
  	 * Adds the layer to the given map or layer group.
  	 */
  	addTo: function (map) {
  		map.addLayer(this);
  		return this;
  	},

  	// @method remove: this
  	// Removes the layer from the map it is currently active on.
  	remove: function () {
  		return this.removeFrom(this._map || this._mapToAdd);
  	},

  	// @method removeFrom(map: Map): this
  	// Removes the layer from the given map
  	//
  	// @alternative
  	// @method removeFrom(group: LayerGroup): this
  	// Removes the layer from the given `LayerGroup`
  	removeFrom: function (obj) {
  		if (obj) {
  			obj.removeLayer(this);
  		}
  		return this;
  	},

  	// @method getPane(name? : String): HTMLElement
  	// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
  	getPane: function (name) {
  		return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
  	},

  	addInteractiveTarget: function (targetEl) {
  		this._map._targets[stamp(targetEl)] = this;
  		return this;
  	},

  	removeInteractiveTarget: function (targetEl) {
  		delete this._map._targets[stamp(targetEl)];
  		return this;
  	},

  	// @method getAttribution: String
  	// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
  	getAttribution: function () {
  		return this.options.attribution;
  	},

  	_layerAdd: function (e) {
  		var map = e.target;

  		// check in case layer gets added and then removed before the map is ready
  		if (!map.hasLayer(this)) { return; }

  		this._map = map;
  		this._zoomAnimated = map._zoomAnimated;

  		if (this.getEvents) {
  			var events = this.getEvents();
  			map.on(events, this);
  			this.once('remove', function () {
  				map.off(events, this);
  			}, this);
  		}

  		this.onAdd(map);

  		this.fire('add');
  		map.fire('layeradd', {layer: this});
  	}
  });

  /* @section Extension methods
   * @uninheritable
   *
   * Every layer should extend from `L.Layer` and (re-)implement the following methods.
   *
   * @method onAdd(map: Map): this
   * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
   *
   * @method onRemove(map: Map): this
   * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
   *
   * @method getEvents(): Object
   * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
   *
   * @method getAttribution(): String
   * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
   *
   * @method beforeAdd(map: Map): this
   * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
   */


  /* @namespace Map
   * @section Layer events
   *
   * @event layeradd: LayerEvent
   * Fired when a new layer is added to the map.
   *
   * @event layerremove: LayerEvent
   * Fired when some layer is removed from the map
   *
   * @section Methods for Layers and Controls
   */
  Map.include({
  	// @method addLayer(layer: Layer): this
  	// Adds the given layer to the map
  	addLayer: function (layer) {
  		if (!layer._layerAdd) {
  			throw new Error('The provided object is not a Layer.');
  		}

  		var id = stamp(layer);
  		if (this._layers[id]) { return this; }
  		this._layers[id] = layer;

  		layer._mapToAdd = this;

  		if (layer.beforeAdd) {
  			layer.beforeAdd(this);
  		}

  		this.whenReady(layer._layerAdd, layer);

  		return this;
  	},

  	// @method removeLayer(layer: Layer): this
  	// Removes the given layer from the map.
  	removeLayer: function (layer) {
  		var id = stamp(layer);

  		if (!this._layers[id]) { return this; }

  		if (this._loaded) {
  			layer.onRemove(this);
  		}

  		delete this._layers[id];

  		if (this._loaded) {
  			this.fire('layerremove', {layer: layer});
  			layer.fire('remove');
  		}

  		layer._map = layer._mapToAdd = null;

  		return this;
  	},

  	// @method hasLayer(layer: Layer): Boolean
  	// Returns `true` if the given layer is currently added to the map
  	hasLayer: function (layer) {
  		return stamp(layer) in this._layers;
  	},

  	/* @method eachLayer(fn: Function, context?: Object): this
  	 * Iterates over the layers of the map, optionally specifying context of the iterator function.
  	 * ```
  	 * map.eachLayer(function(layer){
  	 *     layer.bindPopup('Hello');
  	 * });
  	 * ```
  	 */
  	eachLayer: function (method, context) {
  		for (var i in this._layers) {
  			method.call(context, this._layers[i]);
  		}
  		return this;
  	},

  	_addLayers: function (layers) {
  		layers = layers ? (isArray(layers) ? layers : [layers]) : [];

  		for (var i = 0, len = layers.length; i < len; i++) {
  			this.addLayer(layers[i]);
  		}
  	},

  	_addZoomLimit: function (layer) {
  		if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
  			this._zoomBoundLayers[stamp(layer)] = layer;
  			this._updateZoomLevels();
  		}
  	},

  	_removeZoomLimit: function (layer) {
  		var id = stamp(layer);

  		if (this._zoomBoundLayers[id]) {
  			delete this._zoomBoundLayers[id];
  			this._updateZoomLevels();
  		}
  	},

  	_updateZoomLevels: function () {
  		var minZoom = Infinity,
  		    maxZoom = -Infinity,
  		    oldZoomSpan = this._getZoomSpan();

  		for (var i in this._zoomBoundLayers) {
  			var options = this._zoomBoundLayers[i].options;

  			minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
  			maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
  		}

  		this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
  		this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;

  		// @section Map state change events
  		// @event zoomlevelschange: Event
  		// Fired when the number of zoomlevels on the map is changed due
  		// to adding or removing a layer.
  		if (oldZoomSpan !== this._getZoomSpan()) {
  			this.fire('zoomlevelschange');
  		}

  		if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
  			this.setZoom(this._layersMaxZoom);
  		}
  		if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
  			this.setZoom(this._layersMinZoom);
  		}
  	}
  });

  /*
   * @class LayerGroup
   * @aka L.LayerGroup
   * @inherits Interactive layer
   *
   * Used to group several layers and handle them as one. If you add it to the map,
   * any layers added or removed from the group will be added/removed on the map as
   * well. Extends `Layer`.
   *
   * @example
   *
   * ```js
   * L.layerGroup([marker1, marker2])
   * 	.addLayer(polyline)
   * 	.addTo(map);
   * ```
   */

  var LayerGroup = Layer.extend({

  	initialize: function (layers, options) {
  		setOptions(this, options);

  		this._layers = {};

  		var i, len;

  		if (layers) {
  			for (i = 0, len = layers.length; i < len; i++) {
  				this.addLayer(layers[i]);
  			}
  		}
  	},

  	// @method addLayer(layer: Layer): this
  	// Adds the given layer to the group.
  	addLayer: function (layer) {
  		var id = this.getLayerId(layer);

  		this._layers[id] = layer;

  		if (this._map) {
  			this._map.addLayer(layer);
  		}

  		return this;
  	},

  	// @method removeLayer(layer: Layer): this
  	// Removes the given layer from the group.
  	// @alternative
  	// @method removeLayer(id: Number): this
  	// Removes the layer with the given internal ID from the group.
  	removeLayer: function (layer) {
  		var id = layer in this._layers ? layer : this.getLayerId(layer);

  		if (this._map && this._layers[id]) {
  			this._map.removeLayer(this._layers[id]);
  		}

  		delete this._layers[id];

  		return this;
  	},

  	// @method hasLayer(layer: Layer): Boolean
  	// Returns `true` if the given layer is currently added to the group.
  	// @alternative
  	// @method hasLayer(id: Number): Boolean
  	// Returns `true` if the given internal ID is currently added to the group.
  	hasLayer: function (layer) {
  		var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
  		return layerId in this._layers;
  	},

  	// @method clearLayers(): this
  	// Removes all the layers from the group.
  	clearLayers: function () {
  		return this.eachLayer(this.removeLayer, this);
  	},

  	// @method invoke(methodName: String, …): this
  	// Calls `methodName` on every layer contained in this group, passing any
  	// additional parameters. Has no effect if the layers contained do not
  	// implement `methodName`.
  	invoke: function (methodName) {
  		var args = Array.prototype.slice.call(arguments, 1),
  		    i, layer;

  		for (i in this._layers) {
  			layer = this._layers[i];

  			if (layer[methodName]) {
  				layer[methodName].apply(layer, args);
  			}
  		}

  		return this;
  	},

  	onAdd: function (map) {
  		this.eachLayer(map.addLayer, map);
  	},

  	onRemove: function (map) {
  		this.eachLayer(map.removeLayer, map);
  	},

  	// @method eachLayer(fn: Function, context?: Object): this
  	// Iterates over the layers of the group, optionally specifying context of the iterator function.
  	// ```js
  	// group.eachLayer(function (layer) {
  	// 	layer.bindPopup('Hello');
  	// });
  	// ```
  	eachLayer: function (method, context) {
  		for (var i in this._layers) {
  			method.call(context, this._layers[i]);
  		}
  		return this;
  	},

  	// @method getLayer(id: Number): Layer
  	// Returns the layer with the given internal ID.
  	getLayer: function (id) {
  		return this._layers[id];
  	},

  	// @method getLayers(): Layer[]
  	// Returns an array of all the layers added to the group.
  	getLayers: function () {
  		var layers = [];
  		this.eachLayer(layers.push, layers);
  		return layers;
  	},

  	// @method setZIndex(zIndex: Number): this
  	// Calls `setZIndex` on every layer contained in this group, passing the z-index.
  	setZIndex: function (zIndex) {
  		return this.invoke('setZIndex', zIndex);
  	},

  	// @method getLayerId(layer: Layer): Number
  	// Returns the internal ID for a layer
  	getLayerId: function (layer) {
  		return stamp(layer);
  	}
  });


  // @factory L.layerGroup(layers?: Layer[], options?: Object)
  // Create a layer group, optionally given an initial set of layers and an `options` object.
  var layerGroup = function (layers, options) {
  	return new LayerGroup(layers, options);
  };

  /*
   * @class FeatureGroup
   * @aka L.FeatureGroup
   * @inherits LayerGroup
   *
   * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
   *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
   *  * Events are propagated to the `FeatureGroup`, so if the group has an event
   * handler, it will handle events from any of the layers. This includes mouse events
   * and custom events.
   *  * Has `layeradd` and `layerremove` events
   *
   * @example
   *
   * ```js
   * L.featureGroup([marker1, marker2, polyline])
   * 	.bindPopup('Hello world!')
   * 	.on('click', function() { alert('Clicked on a member of the group!'); })
   * 	.addTo(map);
   * ```
   */

  var FeatureGroup = LayerGroup.extend({

  	addLayer: function (layer) {
  		if (this.hasLayer(layer)) {
  			return this;
  		}

  		layer.addEventParent(this);

  		LayerGroup.prototype.addLayer.call(this, layer);

  		// @event layeradd: LayerEvent
  		// Fired when a layer is added to this `FeatureGroup`
  		return this.fire('layeradd', {layer: layer});
  	},

  	removeLayer: function (layer) {
  		if (!this.hasLayer(layer)) {
  			return this;
  		}
  		if (layer in this._layers) {
  			layer = this._layers[layer];
  		}

  		layer.removeEventParent(this);

  		LayerGroup.prototype.removeLayer.call(this, layer);

  		// @event layerremove: LayerEvent
  		// Fired when a layer is removed from this `FeatureGroup`
  		return this.fire('layerremove', {layer: layer});
  	},

  	// @method setStyle(style: Path options): this
  	// Sets the given path options to each layer of the group that has a `setStyle` method.
  	setStyle: function (style) {
  		return this.invoke('setStyle', style);
  	},

  	// @method bringToFront(): this
  	// Brings the layer group to the top of all other layers
  	bringToFront: function () {
  		return this.invoke('bringToFront');
  	},

  	// @method bringToBack(): this
  	// Brings the layer group to the back of all other layers
  	bringToBack: function () {
  		return this.invoke('bringToBack');
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
  	getBounds: function () {
  		var bounds = new LatLngBounds();

  		for (var id in this._layers) {
  			var layer = this._layers[id];
  			bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
  		}
  		return bounds;
  	}
  });

  // @factory L.featureGroup(layers?: Layer[], options?: Object)
  // Create a feature group, optionally given an initial set of layers and an `options` object.
  var featureGroup = function (layers, options) {
  	return new FeatureGroup(layers, options);
  };

  /*
   * @class Icon
   * @aka L.Icon
   *
   * Represents an icon to provide when creating a marker.
   *
   * @example
   *
   * ```js
   * var myIcon = L.icon({
   *     iconUrl: 'my-icon.png',
   *     iconRetinaUrl: 'my-icon@2x.png',
   *     iconSize: [38, 95],
   *     iconAnchor: [22, 94],
   *     popupAnchor: [-3, -76],
   *     shadowUrl: 'my-icon-shadow.png',
   *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
   *     shadowSize: [68, 95],
   *     shadowAnchor: [22, 94]
   * });
   *
   * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
   * ```
   *
   * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
   *
   */

  var Icon = Class.extend({

  	/* @section
  	 * @aka Icon options
  	 *
  	 * @option iconUrl: String = null
  	 * **(required)** The URL to the icon image (absolute or relative to your script path).
  	 *
  	 * @option iconRetinaUrl: String = null
  	 * The URL to a retina sized version of the icon image (absolute or relative to your
  	 * script path). Used for Retina screen devices.
  	 *
  	 * @option iconSize: Point = null
  	 * Size of the icon image in pixels.
  	 *
  	 * @option iconAnchor: Point = null
  	 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
  	 * will be aligned so that this point is at the marker's geographical location. Centered
  	 * by default if size is specified, also can be set in CSS with negative margins.
  	 *
  	 * @option popupAnchor: Point = [0, 0]
  	 * The coordinates of the point from which popups will "open", relative to the icon anchor.
  	 *
  	 * @option tooltipAnchor: Point = [0, 0]
  	 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
  	 *
  	 * @option shadowUrl: String = null
  	 * The URL to the icon shadow image. If not specified, no shadow image will be created.
  	 *
  	 * @option shadowRetinaUrl: String = null
  	 *
  	 * @option shadowSize: Point = null
  	 * Size of the shadow image in pixels.
  	 *
  	 * @option shadowAnchor: Point = null
  	 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
  	 * as iconAnchor if not specified).
  	 *
  	 * @option className: String = ''
  	 * A custom class name to assign to both icon and shadow images. Empty by default.
  	 */

  	options: {
  		popupAnchor: [0, 0],
  		tooltipAnchor: [0, 0],

  		// @option crossOrigin: Boolean|String = false
  		// Whether the crossOrigin attribute will be added to the tiles.
  		// If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
  		crossOrigin: false
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  	},

  	// @method createIcon(oldIcon?: HTMLElement): HTMLElement
  	// Called internally when the icon has to be shown, returns a `<img>` HTML element
  	// styled according to the options.
  	createIcon: function (oldIcon) {
  		return this._createIcon('icon', oldIcon);
  	},

  	// @method createShadow(oldIcon?: HTMLElement): HTMLElement
  	// As `createIcon`, but for the shadow beneath it.
  	createShadow: function (oldIcon) {
  		return this._createIcon('shadow', oldIcon);
  	},

  	_createIcon: function (name, oldIcon) {
  		var src = this._getIconUrl(name);

  		if (!src) {
  			if (name === 'icon') {
  				throw new Error('iconUrl not set in Icon options (see the docs).');
  			}
  			return null;
  		}

  		var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
  		this._setIconStyles(img, name);

  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
  			img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
  		}

  		return img;
  	},

  	_setIconStyles: function (img, name) {
  		var options = this.options;
  		var sizeOption = options[name + 'Size'];

  		if (typeof sizeOption === 'number') {
  			sizeOption = [sizeOption, sizeOption];
  		}

  		var size = toPoint(sizeOption),
  		    anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
  		            size && size.divideBy(2, true));

  		img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');

  		if (anchor) {
  			img.style.marginLeft = (-anchor.x) + 'px';
  			img.style.marginTop  = (-anchor.y) + 'px';
  		}

  		if (size) {
  			img.style.width  = size.x + 'px';
  			img.style.height = size.y + 'px';
  		}
  	},

  	_createImg: function (src, el) {
  		el = el || document.createElement('img');
  		el.src = src;
  		return el;
  	},

  	_getIconUrl: function (name) {
  		return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
  	}
  });


  // @factory L.icon(options: Icon options)
  // Creates an icon instance with the given options.
  function icon(options) {
  	return new Icon(options);
  }

  /*
   * @miniclass Icon.Default (Icon)
   * @aka L.Icon.Default
   * @section
   *
   * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
   * no icon is specified. Points to the blue marker image distributed with Leaflet
   * releases.
   *
   * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
   * (which is a set of `Icon options`).
   *
   * If you want to _completely_ replace the default icon, override the
   * `L.Marker.prototype.options.icon` with your own icon instead.
   */

  var IconDefault = Icon.extend({

  	options: {
  		iconUrl: 'marker-icon.png',
  		iconSize: [25, 41],
  		iconAnchor: [12, 41],
  		popupAnchor: [1, -34],
  		tooltipAnchor: [16, -28],
  		shadowSize: [41, 41]
  	},

  	_getIconUrl: function (name) {
  		if (typeof IconDefault.imagePath !== 'string') {	// Deprecated, backwards-compatibility only
  			IconDefault.imagePath = this._detectIconPath();
  		}

  		// @option imagePath: String
  		// `Icon.Default` will try to auto-detect the location of the
  		// blue icon images. If you are placing these images in a non-standard
  		// way, set this option to point to the right path.
  		return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
  	},

  	_stripUrl: function (path) {	// separate function to use in tests
  		var strip = function (str, re, idx) {
  			var match = re.exec(str);
  			return match && match[idx];
  		};
  		path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
  		return path && strip(path, /^(.*)marker-icon\.png$/, 1);
  	},

  	_detectIconPath: function () {
  		var el = create$1('div',  'leaflet-default-icon-path', document.body);
  		var path = getStyle(el, 'background-image') ||
  		           getStyle(el, 'backgroundImage');	// IE8

  		document.body.removeChild(el);
  		path = this._stripUrl(path);
  		if (path) { return path; }
  		var link = document.querySelector('link[href$="leaflet.css"]');
  		if (!link) { return ''; }
  		return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
  	}
  });

  /*
   * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
   */


  /* @namespace Marker
   * @section Interaction handlers
   *
   * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
   *
   * ```js
   * marker.dragging.disable();
   * ```
   *
   * @property dragging: Handler
   * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
   */

  var MarkerDrag = Handler.extend({
  	initialize: function (marker) {
  		this._marker = marker;
  	},

  	addHooks: function () {
  		var icon = this._marker._icon;

  		if (!this._draggable) {
  			this._draggable = new Draggable(icon, icon, true);
  		}

  		this._draggable.on({
  			dragstart: this._onDragStart,
  			predrag: this._onPreDrag,
  			drag: this._onDrag,
  			dragend: this._onDragEnd
  		}, this).enable();

  		addClass(icon, 'leaflet-marker-draggable');
  	},

  	removeHooks: function () {
  		this._draggable.off({
  			dragstart: this._onDragStart,
  			predrag: this._onPreDrag,
  			drag: this._onDrag,
  			dragend: this._onDragEnd
  		}, this).disable();

  		if (this._marker._icon) {
  			removeClass(this._marker._icon, 'leaflet-marker-draggable');
  		}
  	},

  	moved: function () {
  		return this._draggable && this._draggable._moved;
  	},

  	_adjustPan: function (e) {
  		var marker = this._marker,
  		    map = marker._map,
  		    speed = this._marker.options.autoPanSpeed,
  		    padding = this._marker.options.autoPanPadding,
  		    iconPos = getPosition(marker._icon),
  		    bounds = map.getPixelBounds(),
  		    origin = map.getPixelOrigin();

  		var panBounds = toBounds(
  			bounds.min._subtract(origin).add(padding),
  			bounds.max._subtract(origin).subtract(padding)
  		);

  		if (!panBounds.contains(iconPos)) {
  			// Compute incremental movement
  			var movement = toPoint(
  				(Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
  				(Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),

  				(Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
  				(Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
  			).multiplyBy(speed);

  			map.panBy(movement, {animate: false});

  			this._draggable._newPos._add(movement);
  			this._draggable._startPos._add(movement);

  			setPosition(marker._icon, this._draggable._newPos);
  			this._onDrag(e);

  			this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
  		}
  	},

  	_onDragStart: function () {
  		// @section Dragging events
  		// @event dragstart: Event
  		// Fired when the user starts dragging the marker.

  		// @event movestart: Event
  		// Fired when the marker starts moving (because of dragging).

  		this._oldLatLng = this._marker.getLatLng();

  		// When using ES6 imports it could not be set when `Popup` was not imported as well
  		this._marker.closePopup && this._marker.closePopup();

  		this._marker
  			.fire('movestart')
  			.fire('dragstart');
  	},

  	_onPreDrag: function (e) {
  		if (this._marker.options.autoPan) {
  			cancelAnimFrame(this._panRequest);
  			this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
  		}
  	},

  	_onDrag: function (e) {
  		var marker = this._marker,
  		    shadow = marker._shadow,
  		    iconPos = getPosition(marker._icon),
  		    latlng = marker._map.layerPointToLatLng(iconPos);

  		// update shadow position
  		if (shadow) {
  			setPosition(shadow, iconPos);
  		}

  		marker._latlng = latlng;
  		e.latlng = latlng;
  		e.oldLatLng = this._oldLatLng;

  		// @event drag: Event
  		// Fired repeatedly while the user drags the marker.
  		marker
  		    .fire('move', e)
  		    .fire('drag', e);
  	},

  	_onDragEnd: function (e) {
  		// @event dragend: DragEndEvent
  		// Fired when the user stops dragging the marker.

  		 cancelAnimFrame(this._panRequest);

  		// @event moveend: Event
  		// Fired when the marker stops moving (because of dragging).
  		delete this._oldLatLng;
  		this._marker
  		    .fire('moveend')
  		    .fire('dragend', e);
  	}
  });

  /*
   * @class Marker
   * @inherits Interactive layer
   * @aka L.Marker
   * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
   *
   * @example
   *
   * ```js
   * L.marker([50.5, 30.5]).addTo(map);
   * ```
   */

  var Marker = Layer.extend({

  	// @section
  	// @aka Marker options
  	options: {
  		// @option icon: Icon = *
  		// Icon instance to use for rendering the marker.
  		// See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
  		// If not specified, a common instance of `L.Icon.Default` is used.
  		icon: new IconDefault(),

  		// Option inherited from "Interactive layer" abstract class
  		interactive: true,

  		// @option keyboard: Boolean = true
  		// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
  		keyboard: true,

  		// @option title: String = ''
  		// Text for the browser tooltip that appear on marker hover (no tooltip by default).
  		// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
  		title: '',

  		// @option alt: String = 'Marker'
  		// Text for the `alt` attribute of the icon image.
  		// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
  		alt: 'Marker',

  		// @option zIndexOffset: Number = 0
  		// By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
  		zIndexOffset: 0,

  		// @option opacity: Number = 1.0
  		// The opacity of the marker.
  		opacity: 1,

  		// @option riseOnHover: Boolean = false
  		// If `true`, the marker will get on top of others when you hover the mouse over it.
  		riseOnHover: false,

  		// @option riseOffset: Number = 250
  		// The z-index offset used for the `riseOnHover` feature.
  		riseOffset: 250,

  		// @option pane: String = 'markerPane'
  		// `Map pane` where the markers icon will be added.
  		pane: 'markerPane',

  		// @option shadowPane: String = 'shadowPane'
  		// `Map pane` where the markers shadow will be added.
  		shadowPane: 'shadowPane',

  		// @option bubblingMouseEvents: Boolean = false
  		// When `true`, a mouse event on this marker will trigger the same event on the map
  		// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
  		bubblingMouseEvents: false,

  		// @option autoPanOnFocus: Boolean = true
  		// When `true`, the map will pan whenever the marker is focused (via
  		// e.g. pressing `tab` on the keyboard) to ensure the marker is
  		// visible within the map's bounds
  		autoPanOnFocus: true,

  		// @section Draggable marker options
  		// @option draggable: Boolean = false
  		// Whether the marker is draggable with mouse/touch or not.
  		draggable: false,

  		// @option autoPan: Boolean = false
  		// Whether to pan the map when dragging this marker near its edge or not.
  		autoPan: false,

  		// @option autoPanPadding: Point = Point(50, 50)
  		// Distance (in pixels to the left/right and to the top/bottom) of the
  		// map edge to start panning the map.
  		autoPanPadding: [50, 50],

  		// @option autoPanSpeed: Number = 10
  		// Number of pixels the map should pan by.
  		autoPanSpeed: 10
  	},

  	/* @section
  	 *
  	 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
  	 */

  	initialize: function (latlng, options) {
  		setOptions(this, options);
  		this._latlng = toLatLng(latlng);
  	},

  	onAdd: function (map) {
  		this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;

  		if (this._zoomAnimated) {
  			map.on('zoomanim', this._animateZoom, this);
  		}

  		this._initIcon();
  		this.update();
  	},

  	onRemove: function (map) {
  		if (this.dragging && this.dragging.enabled()) {
  			this.options.draggable = true;
  			this.dragging.removeHooks();
  		}
  		delete this.dragging;

  		if (this._zoomAnimated) {
  			map.off('zoomanim', this._animateZoom, this);
  		}

  		this._removeIcon();
  		this._removeShadow();
  	},

  	getEvents: function () {
  		return {
  			zoom: this.update,
  			viewreset: this.update
  		};
  	},

  	// @method getLatLng: LatLng
  	// Returns the current geographical position of the marker.
  	getLatLng: function () {
  		return this._latlng;
  	},

  	// @method setLatLng(latlng: LatLng): this
  	// Changes the marker position to the given point.
  	setLatLng: function (latlng) {
  		var oldLatLng = this._latlng;
  		this._latlng = toLatLng(latlng);
  		this.update();

  		// @event move: Event
  		// Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
  		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
  	},

  	// @method setZIndexOffset(offset: Number): this
  	// Changes the [zIndex offset](#marker-zindexoffset) of the marker.
  	setZIndexOffset: function (offset) {
  		this.options.zIndexOffset = offset;
  		return this.update();
  	},

  	// @method getIcon: Icon
  	// Returns the current icon used by the marker
  	getIcon: function () {
  		return this.options.icon;
  	},

  	// @method setIcon(icon: Icon): this
  	// Changes the marker icon.
  	setIcon: function (icon) {

  		this.options.icon = icon;

  		if (this._map) {
  			this._initIcon();
  			this.update();
  		}

  		if (this._popup) {
  			this.bindPopup(this._popup, this._popup.options);
  		}

  		return this;
  	},

  	getElement: function () {
  		return this._icon;
  	},

  	update: function () {

  		if (this._icon && this._map) {
  			var pos = this._map.latLngToLayerPoint(this._latlng).round();
  			this._setPos(pos);
  		}

  		return this;
  	},

  	_initIcon: function () {
  		var options = this.options,
  		    classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

  		var icon = options.icon.createIcon(this._icon),
  		    addIcon = false;

  		// if we're not reusing the icon, remove the old one and init new one
  		if (icon !== this._icon) {
  			if (this._icon) {
  				this._removeIcon();
  			}
  			addIcon = true;

  			if (options.title) {
  				icon.title = options.title;
  			}

  			if (icon.tagName === 'IMG') {
  				icon.alt = options.alt || '';
  			}
  		}

  		addClass(icon, classToAdd);

  		if (options.keyboard) {
  			icon.tabIndex = '0';
  			icon.setAttribute('role', 'button');
  		}

  		this._icon = icon;

  		if (options.riseOnHover) {
  			this.on({
  				mouseover: this._bringToFront,
  				mouseout: this._resetZIndex
  			});
  		}

  		if (this.options.autoPanOnFocus) {
  			on(icon, 'focus', this._panOnFocus, this);
  		}

  		var newShadow = options.icon.createShadow(this._shadow),
  		    addShadow = false;

  		if (newShadow !== this._shadow) {
  			this._removeShadow();
  			addShadow = true;
  		}

  		if (newShadow) {
  			addClass(newShadow, classToAdd);
  			newShadow.alt = '';
  		}
  		this._shadow = newShadow;


  		if (options.opacity < 1) {
  			this._updateOpacity();
  		}


  		if (addIcon) {
  			this.getPane().appendChild(this._icon);
  		}
  		this._initInteraction();
  		if (newShadow && addShadow) {
  			this.getPane(options.shadowPane).appendChild(this._shadow);
  		}
  	},

  	_removeIcon: function () {
  		if (this.options.riseOnHover) {
  			this.off({
  				mouseover: this._bringToFront,
  				mouseout: this._resetZIndex
  			});
  		}

  		if (this.options.autoPanOnFocus) {
  			off(this._icon, 'focus', this._panOnFocus, this);
  		}

  		remove(this._icon);
  		this.removeInteractiveTarget(this._icon);

  		this._icon = null;
  	},

  	_removeShadow: function () {
  		if (this._shadow) {
  			remove(this._shadow);
  		}
  		this._shadow = null;
  	},

  	_setPos: function (pos) {

  		if (this._icon) {
  			setPosition(this._icon, pos);
  		}

  		if (this._shadow) {
  			setPosition(this._shadow, pos);
  		}

  		this._zIndex = pos.y + this.options.zIndexOffset;

  		this._resetZIndex();
  	},

  	_updateZIndex: function (offset) {
  		if (this._icon) {
  			this._icon.style.zIndex = this._zIndex + offset;
  		}
  	},

  	_animateZoom: function (opt) {
  		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();

  		this._setPos(pos);
  	},

  	_initInteraction: function () {

  		if (!this.options.interactive) { return; }

  		addClass(this._icon, 'leaflet-interactive');

  		this.addInteractiveTarget(this._icon);

  		if (MarkerDrag) {
  			var draggable = this.options.draggable;
  			if (this.dragging) {
  				draggable = this.dragging.enabled();
  				this.dragging.disable();
  			}

  			this.dragging = new MarkerDrag(this);

  			if (draggable) {
  				this.dragging.enable();
  			}
  		}
  	},

  	// @method setOpacity(opacity: Number): this
  	// Changes the opacity of the marker.
  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;
  		if (this._map) {
  			this._updateOpacity();
  		}

  		return this;
  	},

  	_updateOpacity: function () {
  		var opacity = this.options.opacity;

  		if (this._icon) {
  			setOpacity(this._icon, opacity);
  		}

  		if (this._shadow) {
  			setOpacity(this._shadow, opacity);
  		}
  	},

  	_bringToFront: function () {
  		this._updateZIndex(this.options.riseOffset);
  	},

  	_resetZIndex: function () {
  		this._updateZIndex(0);
  	},

  	_panOnFocus: function () {
  		var map = this._map;
  		if (!map) { return; }

  		var iconOpts = this.options.icon.options;
  		var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
  		var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);

  		map.panInside(this._latlng, {
  			paddingTopLeft: anchor,
  			paddingBottomRight: size.subtract(anchor)
  		});
  	},

  	_getPopupAnchor: function () {
  		return this.options.icon.options.popupAnchor;
  	},

  	_getTooltipAnchor: function () {
  		return this.options.icon.options.tooltipAnchor;
  	}
  });


  // factory L.marker(latlng: LatLng, options? : Marker options)

  // @factory L.marker(latlng: LatLng, options? : Marker options)
  // Instantiates a Marker object given a geographical point and optionally an options object.
  function marker(latlng, options) {
  	return new Marker(latlng, options);
  }

  /*
   * @class Path
   * @aka L.Path
   * @inherits Interactive layer
   *
   * An abstract class that contains options and constants shared between vector
   * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
   */

  var Path = Layer.extend({

  	// @section
  	// @aka Path options
  	options: {
  		// @option stroke: Boolean = true
  		// Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
  		stroke: true,

  		// @option color: String = '#3388ff'
  		// Stroke color
  		color: '#3388ff',

  		// @option weight: Number = 3
  		// Stroke width in pixels
  		weight: 3,

  		// @option opacity: Number = 1.0
  		// Stroke opacity
  		opacity: 1,

  		// @option lineCap: String= 'round'
  		// A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
  		lineCap: 'round',

  		// @option lineJoin: String = 'round'
  		// A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
  		lineJoin: 'round',

  		// @option dashArray: String = null
  		// A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
  		dashArray: null,

  		// @option dashOffset: String = null
  		// A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
  		dashOffset: null,

  		// @option fill: Boolean = depends
  		// Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
  		fill: false,

  		// @option fillColor: String = *
  		// Fill color. Defaults to the value of the [`color`](#path-color) option
  		fillColor: null,

  		// @option fillOpacity: Number = 0.2
  		// Fill opacity.
  		fillOpacity: 0.2,

  		// @option fillRule: String = 'evenodd'
  		// A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
  		fillRule: 'evenodd',

  		// className: '',

  		// Option inherited from "Interactive layer" abstract class
  		interactive: true,

  		// @option bubblingMouseEvents: Boolean = true
  		// When `true`, a mouse event on this path will trigger the same event on the map
  		// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
  		bubblingMouseEvents: true
  	},

  	beforeAdd: function (map) {
  		// Renderer is set here because we need to call renderer.getEvents
  		// before this.getEvents.
  		this._renderer = map.getRenderer(this);
  	},

  	onAdd: function () {
  		this._renderer._initPath(this);
  		this._reset();
  		this._renderer._addPath(this);
  	},

  	onRemove: function () {
  		this._renderer._removePath(this);
  	},

  	// @method redraw(): this
  	// Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
  	redraw: function () {
  		if (this._map) {
  			this._renderer._updatePath(this);
  		}
  		return this;
  	},

  	// @method setStyle(style: Path options): this
  	// Changes the appearance of a Path based on the options in the `Path options` object.
  	setStyle: function (style) {
  		setOptions(this, style);
  		if (this._renderer) {
  			this._renderer._updateStyle(this);
  			if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
  				this._updateBounds();
  			}
  		}
  		return this;
  	},

  	// @method bringToFront(): this
  	// Brings the layer to the top of all path layers.
  	bringToFront: function () {
  		if (this._renderer) {
  			this._renderer._bringToFront(this);
  		}
  		return this;
  	},

  	// @method bringToBack(): this
  	// Brings the layer to the bottom of all path layers.
  	bringToBack: function () {
  		if (this._renderer) {
  			this._renderer._bringToBack(this);
  		}
  		return this;
  	},

  	getElement: function () {
  		return this._path;
  	},

  	_reset: function () {
  		// defined in child classes
  		this._project();
  		this._update();
  	},

  	_clickTolerance: function () {
  		// used when doing hit detection for Canvas layers
  		return (this.options.stroke ? this.options.weight / 2 : 0) +
  		  (this._renderer.options.tolerance || 0);
  	}
  });

  /*
   * @class CircleMarker
   * @aka L.CircleMarker
   * @inherits Path
   *
   * A circle of a fixed size with radius specified in pixels. Extends `Path`.
   */

  var CircleMarker = Path.extend({

  	// @section
  	// @aka CircleMarker options
  	options: {
  		fill: true,

  		// @option radius: Number = 10
  		// Radius of the circle marker, in pixels
  		radius: 10
  	},

  	initialize: function (latlng, options) {
  		setOptions(this, options);
  		this._latlng = toLatLng(latlng);
  		this._radius = this.options.radius;
  	},

  	// @method setLatLng(latLng: LatLng): this
  	// Sets the position of a circle marker to a new location.
  	setLatLng: function (latlng) {
  		var oldLatLng = this._latlng;
  		this._latlng = toLatLng(latlng);
  		this.redraw();

  		// @event move: Event
  		// Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
  		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
  	},

  	// @method getLatLng(): LatLng
  	// Returns the current geographical position of the circle marker
  	getLatLng: function () {
  		return this._latlng;
  	},

  	// @method setRadius(radius: Number): this
  	// Sets the radius of a circle marker. Units are in pixels.
  	setRadius: function (radius) {
  		this.options.radius = this._radius = radius;
  		return this.redraw();
  	},

  	// @method getRadius(): Number
  	// Returns the current radius of the circle
  	getRadius: function () {
  		return this._radius;
  	},

  	setStyle : function (options) {
  		var radius = options && options.radius || this._radius;
  		Path.prototype.setStyle.call(this, options);
  		this.setRadius(radius);
  		return this;
  	},

  	_project: function () {
  		this._point = this._map.latLngToLayerPoint(this._latlng);
  		this._updateBounds();
  	},

  	_updateBounds: function () {
  		var r = this._radius,
  		    r2 = this._radiusY || r,
  		    w = this._clickTolerance(),
  		    p = [r + w, r2 + w];
  		this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
  	},

  	_update: function () {
  		if (this._map) {
  			this._updatePath();
  		}
  	},

  	_updatePath: function () {
  		this._renderer._updateCircle(this);
  	},

  	_empty: function () {
  		return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
  	},

  	// Needed by the `Canvas` renderer for interactivity
  	_containsPoint: function (p) {
  		return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
  	}
  });


  // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
  // Instantiates a circle marker object given a geographical point, and an optional options object.
  function circleMarker(latlng, options) {
  	return new CircleMarker(latlng, options);
  }

  /*
   * @class Circle
   * @aka L.Circle
   * @inherits CircleMarker
   *
   * A class for drawing circle overlays on a map. Extends `CircleMarker`.
   *
   * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
   *
   * @example
   *
   * ```js
   * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
   * ```
   */

  var Circle = CircleMarker.extend({

  	initialize: function (latlng, options, legacyOptions) {
  		if (typeof options === 'number') {
  			// Backwards compatibility with 0.7.x factory (latlng, radius, options?)
  			options = extend({}, legacyOptions, {radius: options});
  		}
  		setOptions(this, options);
  		this._latlng = toLatLng(latlng);

  		if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }

  		// @section
  		// @aka Circle options
  		// @option radius: Number; Radius of the circle, in meters.
  		this._mRadius = this.options.radius;
  	},

  	// @method setRadius(radius: Number): this
  	// Sets the radius of a circle. Units are in meters.
  	setRadius: function (radius) {
  		this._mRadius = radius;
  		return this.redraw();
  	},

  	// @method getRadius(): Number
  	// Returns the current radius of a circle. Units are in meters.
  	getRadius: function () {
  		return this._mRadius;
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the `LatLngBounds` of the path.
  	getBounds: function () {
  		var half = [this._radius, this._radiusY || this._radius];

  		return new LatLngBounds(
  			this._map.layerPointToLatLng(this._point.subtract(half)),
  			this._map.layerPointToLatLng(this._point.add(half)));
  	},

  	setStyle: Path.prototype.setStyle,

  	_project: function () {

  		var lng = this._latlng.lng,
  		    lat = this._latlng.lat,
  		    map = this._map,
  		    crs = map.options.crs;

  		if (crs.distance === Earth.distance) {
  			var d = Math.PI / 180,
  			    latR = (this._mRadius / Earth.R) / d,
  			    top = map.project([lat + latR, lng]),
  			    bottom = map.project([lat - latR, lng]),
  			    p = top.add(bottom).divideBy(2),
  			    lat2 = map.unproject(p).lat,
  			    lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
  			            (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;

  			if (isNaN(lngR) || lngR === 0) {
  				lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
  			}

  			this._point = p.subtract(map.getPixelOrigin());
  			this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
  			this._radiusY = p.y - top.y;

  		} else {
  			var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));

  			this._point = map.latLngToLayerPoint(this._latlng);
  			this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
  		}

  		this._updateBounds();
  	}
  });

  // @factory L.circle(latlng: LatLng, options?: Circle options)
  // Instantiates a circle object given a geographical point, and an options object
  // which contains the circle radius.
  // @alternative
  // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
  // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
  // Do not use in new applications or plugins.
  function circle(latlng, options, legacyOptions) {
  	return new Circle(latlng, options, legacyOptions);
  }

  /*
   * @class Polyline
   * @aka L.Polyline
   * @inherits Path
   *
   * A class for drawing polyline overlays on a map. Extends `Path`.
   *
   * @example
   *
   * ```js
   * // create a red polyline from an array of LatLng points
   * var latlngs = [
   * 	[45.51, -122.68],
   * 	[37.77, -122.43],
   * 	[34.04, -118.2]
   * ];
   *
   * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
   *
   * // zoom the map to the polyline
   * map.fitBounds(polyline.getBounds());
   * ```
   *
   * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
   *
   * ```js
   * // create a red polyline from an array of arrays of LatLng points
   * var latlngs = [
   * 	[[45.51, -122.68],
   * 	 [37.77, -122.43],
   * 	 [34.04, -118.2]],
   * 	[[40.78, -73.91],
   * 	 [41.83, -87.62],
   * 	 [32.76, -96.72]]
   * ];
   * ```
   */


  var Polyline = Path.extend({

  	// @section
  	// @aka Polyline options
  	options: {
  		// @option smoothFactor: Number = 1.0
  		// How much to simplify the polyline on each zoom level. More means
  		// better performance and smoother look, and less means more accurate representation.
  		smoothFactor: 1.0,

  		// @option noClip: Boolean = false
  		// Disable polyline clipping.
  		noClip: false
  	},

  	initialize: function (latlngs, options) {
  		setOptions(this, options);
  		this._setLatLngs(latlngs);
  	},

  	// @method getLatLngs(): LatLng[]
  	// Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
  	getLatLngs: function () {
  		return this._latlngs;
  	},

  	// @method setLatLngs(latlngs: LatLng[]): this
  	// Replaces all the points in the polyline with the given array of geographical points.
  	setLatLngs: function (latlngs) {
  		this._setLatLngs(latlngs);
  		return this.redraw();
  	},

  	// @method isEmpty(): Boolean
  	// Returns `true` if the Polyline has no LatLngs.
  	isEmpty: function () {
  		return !this._latlngs.length;
  	},

  	// @method closestLayerPoint(p: Point): Point
  	// Returns the point closest to `p` on the Polyline.
  	closestLayerPoint: function (p) {
  		var minDistance = Infinity,
  		    minPoint = null,
  		    closest = _sqClosestPointOnSegment,
  		    p1, p2;

  		for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
  			var points = this._parts[j];

  			for (var i = 1, len = points.length; i < len; i++) {
  				p1 = points[i - 1];
  				p2 = points[i];

  				var sqDist = closest(p, p1, p2, true);

  				if (sqDist < minDistance) {
  					minDistance = sqDist;
  					minPoint = closest(p, p1, p2);
  				}
  			}
  		}
  		if (minPoint) {
  			minPoint.distance = Math.sqrt(minDistance);
  		}
  		return minPoint;
  	},

  	// @method getCenter(): LatLng
  	// Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
  	getCenter: function () {
  		// throws error when not yet added to map as this center calculation requires projected coordinates
  		if (!this._map) {
  			throw new Error('Must add layer to map before using getCenter()');
  		}

  		var i, halfDist, segDist, dist, p1, p2, ratio,
  		    points = this._rings[0],
  		    len = points.length;

  		if (!len) { return null; }

  		// polyline centroid algorithm; only uses the first ring if there are multiple

  		for (i = 0, halfDist = 0; i < len - 1; i++) {
  			halfDist += points[i].distanceTo(points[i + 1]) / 2;
  		}

  		// The line is so small in the current view that all points are on the same pixel.
  		if (halfDist === 0) {
  			return this._map.layerPointToLatLng(points[0]);
  		}

  		for (i = 0, dist = 0; i < len - 1; i++) {
  			p1 = points[i];
  			p2 = points[i + 1];
  			segDist = p1.distanceTo(p2);
  			dist += segDist;

  			if (dist > halfDist) {
  				ratio = (dist - halfDist) / segDist;
  				return this._map.layerPointToLatLng([
  					p2.x - ratio * (p2.x - p1.x),
  					p2.y - ratio * (p2.y - p1.y)
  				]);
  			}
  		}
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the `LatLngBounds` of the path.
  	getBounds: function () {
  		return this._bounds;
  	},

  	// @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
  	// Adds a given point to the polyline. By default, adds to the first ring of
  	// the polyline in case of a multi-polyline, but can be overridden by passing
  	// a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
  	addLatLng: function (latlng, latlngs) {
  		latlngs = latlngs || this._defaultShape();
  		latlng = toLatLng(latlng);
  		latlngs.push(latlng);
  		this._bounds.extend(latlng);
  		return this.redraw();
  	},

  	_setLatLngs: function (latlngs) {
  		this._bounds = new LatLngBounds();
  		this._latlngs = this._convertLatLngs(latlngs);
  	},

  	_defaultShape: function () {
  		return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
  	},

  	// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
  	_convertLatLngs: function (latlngs) {
  		var result = [],
  		    flat = isFlat(latlngs);

  		for (var i = 0, len = latlngs.length; i < len; i++) {
  			if (flat) {
  				result[i] = toLatLng(latlngs[i]);
  				this._bounds.extend(result[i]);
  			} else {
  				result[i] = this._convertLatLngs(latlngs[i]);
  			}
  		}

  		return result;
  	},

  	_project: function () {
  		var pxBounds = new Bounds();
  		this._rings = [];
  		this._projectLatlngs(this._latlngs, this._rings, pxBounds);

  		if (this._bounds.isValid() && pxBounds.isValid()) {
  			this._rawPxBounds = pxBounds;
  			this._updateBounds();
  		}
  	},

  	_updateBounds: function () {
  		var w = this._clickTolerance(),
  		    p = new Point(w, w);

  		if (!this._rawPxBounds) {
  			return;
  		}

  		this._pxBounds = new Bounds([
  			this._rawPxBounds.min.subtract(p),
  			this._rawPxBounds.max.add(p)
  		]);
  	},

  	// recursively turns latlngs into a set of rings with projected coordinates
  	_projectLatlngs: function (latlngs, result, projectedBounds) {
  		var flat = latlngs[0] instanceof LatLng,
  		    len = latlngs.length,
  		    i, ring;

  		if (flat) {
  			ring = [];
  			for (i = 0; i < len; i++) {
  				ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
  				projectedBounds.extend(ring[i]);
  			}
  			result.push(ring);
  		} else {
  			for (i = 0; i < len; i++) {
  				this._projectLatlngs(latlngs[i], result, projectedBounds);
  			}
  		}
  	},

  	// clip polyline by renderer bounds so that we have less to render for performance
  	_clipPoints: function () {
  		var bounds = this._renderer._bounds;

  		this._parts = [];
  		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
  			return;
  		}

  		if (this.options.noClip) {
  			this._parts = this._rings;
  			return;
  		}

  		var parts = this._parts,
  		    i, j, k, len, len2, segment, points;

  		for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
  			points = this._rings[i];

  			for (j = 0, len2 = points.length; j < len2 - 1; j++) {
  				segment = clipSegment(points[j], points[j + 1], bounds, j, true);

  				if (!segment) { continue; }

  				parts[k] = parts[k] || [];
  				parts[k].push(segment[0]);

  				// if segment goes out of screen, or it's the last one, it's the end of the line part
  				if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
  					parts[k].push(segment[1]);
  					k++;
  				}
  			}
  		}
  	},

  	// simplify each clipped part of the polyline for performance
  	_simplifyPoints: function () {
  		var parts = this._parts,
  		    tolerance = this.options.smoothFactor;

  		for (var i = 0, len = parts.length; i < len; i++) {
  			parts[i] = simplify(parts[i], tolerance);
  		}
  	},

  	_update: function () {
  		if (!this._map) { return; }

  		this._clipPoints();
  		this._simplifyPoints();
  		this._updatePath();
  	},

  	_updatePath: function () {
  		this._renderer._updatePoly(this);
  	},

  	// Needed by the `Canvas` renderer for interactivity
  	_containsPoint: function (p, closed) {
  		var i, j, k, len, len2, part,
  		    w = this._clickTolerance();

  		if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }

  		// hit detection for polylines
  		for (i = 0, len = this._parts.length; i < len; i++) {
  			part = this._parts[i];

  			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
  				if (!closed && (j === 0)) { continue; }

  				if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
  					return true;
  				}
  			}
  		}
  		return false;
  	}
  });

  // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
  // Instantiates a polyline object given an array of geographical points and
  // optionally an options object. You can create a `Polyline` object with
  // multiple separate lines (`MultiPolyline`) by passing an array of arrays
  // of geographic points.
  function polyline(latlngs, options) {
  	return new Polyline(latlngs, options);
  }

  // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
  Polyline._flat = _flat;

  /*
   * @class Polygon
   * @aka L.Polygon
   * @inherits Polyline
   *
   * A class for drawing polygon overlays on a map. Extends `Polyline`.
   *
   * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
   *
   *
   * @example
   *
   * ```js
   * // create a red polygon from an array of LatLng points
   * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
   *
   * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
   *
   * // zoom the map to the polygon
   * map.fitBounds(polygon.getBounds());
   * ```
   *
   * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
   *
   * ```js
   * var latlngs = [
   *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
   *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
   * ];
   * ```
   *
   * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
   *
   * ```js
   * var latlngs = [
   *   [ // first polygon
   *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
   *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
   *   ],
   *   [ // second polygon
   *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
   *   ]
   * ];
   * ```
   */

  var Polygon = Polyline.extend({

  	options: {
  		fill: true
  	},

  	isEmpty: function () {
  		return !this._latlngs.length || !this._latlngs[0].length;
  	},

  	getCenter: function () {
  		// throws error when not yet added to map as this center calculation requires projected coordinates
  		if (!this._map) {
  			throw new Error('Must add layer to map before using getCenter()');
  		}

  		var i, j, p1, p2, f, area, x, y, center,
  		    points = this._rings[0],
  		    len = points.length;

  		if (!len) { return null; }

  		// polygon centroid algorithm; only uses the first ring if there are multiple

  		area = x = y = 0;

  		for (i = 0, j = len - 1; i < len; j = i++) {
  			p1 = points[i];
  			p2 = points[j];

  			f = p1.y * p2.x - p2.y * p1.x;
  			x += (p1.x + p2.x) * f;
  			y += (p1.y + p2.y) * f;
  			area += f * 3;
  		}

  		if (area === 0) {
  			// Polygon is so small that all points are on same pixel.
  			center = points[0];
  		} else {
  			center = [x / area, y / area];
  		}
  		return this._map.layerPointToLatLng(center);
  	},

  	_convertLatLngs: function (latlngs) {
  		var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
  		    len = result.length;

  		// remove last point if it equals first one
  		if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
  			result.pop();
  		}
  		return result;
  	},

  	_setLatLngs: function (latlngs) {
  		Polyline.prototype._setLatLngs.call(this, latlngs);
  		if (isFlat(this._latlngs)) {
  			this._latlngs = [this._latlngs];
  		}
  	},

  	_defaultShape: function () {
  		return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
  	},

  	_clipPoints: function () {
  		// polygons need a different clipping algorithm so we redefine that

  		var bounds = this._renderer._bounds,
  		    w = this.options.weight,
  		    p = new Point(w, w);

  		// increase clip padding by stroke width to avoid stroke on clip edges
  		bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));

  		this._parts = [];
  		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
  			return;
  		}

  		if (this.options.noClip) {
  			this._parts = this._rings;
  			return;
  		}

  		for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
  			clipped = clipPolygon(this._rings[i], bounds, true);
  			if (clipped.length) {
  				this._parts.push(clipped);
  			}
  		}
  	},

  	_updatePath: function () {
  		this._renderer._updatePoly(this, true);
  	},

  	// Needed by the `Canvas` renderer for interactivity
  	_containsPoint: function (p) {
  		var inside = false,
  		    part, p1, p2, i, j, k, len, len2;

  		if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }

  		// ray casting algorithm for detecting if point is in polygon
  		for (i = 0, len = this._parts.length; i < len; i++) {
  			part = this._parts[i];

  			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
  				p1 = part[j];
  				p2 = part[k];

  				if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
  					inside = !inside;
  				}
  			}
  		}

  		// also check if it's on polygon stroke
  		return inside || Polyline.prototype._containsPoint.call(this, p, true);
  	}

  });


  // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
  function polygon(latlngs, options) {
  	return new Polygon(latlngs, options);
  }

  /*
   * @class GeoJSON
   * @aka L.GeoJSON
   * @inherits FeatureGroup
   *
   * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
   * GeoJSON data and display it on the map. Extends `FeatureGroup`.
   *
   * @example
   *
   * ```js
   * L.geoJSON(data, {
   * 	style: function (feature) {
   * 		return {color: feature.properties.color};
   * 	}
   * }).bindPopup(function (layer) {
   * 	return layer.feature.properties.description;
   * }).addTo(map);
   * ```
   */

  var GeoJSON = FeatureGroup.extend({

  	/* @section
  	 * @aka GeoJSON options
  	 *
  	 * @option pointToLayer: Function = *
  	 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
  	 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
  	 * The default is to spawn a default `Marker`:
  	 * ```js
  	 * function(geoJsonPoint, latlng) {
  	 * 	return L.marker(latlng);
  	 * }
  	 * ```
  	 *
  	 * @option style: Function = *
  	 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
  	 * called internally when data is added.
  	 * The default value is to not override any defaults:
  	 * ```js
  	 * function (geoJsonFeature) {
  	 * 	return {}
  	 * }
  	 * ```
  	 *
  	 * @option onEachFeature: Function = *
  	 * A `Function` that will be called once for each created `Feature`, after it has
  	 * been created and styled. Useful for attaching events and popups to features.
  	 * The default is to do nothing with the newly created layers:
  	 * ```js
  	 * function (feature, layer) {}
  	 * ```
  	 *
  	 * @option filter: Function = *
  	 * A `Function` that will be used to decide whether to include a feature or not.
  	 * The default is to include all features:
  	 * ```js
  	 * function (geoJsonFeature) {
  	 * 	return true;
  	 * }
  	 * ```
  	 * Note: dynamically changing the `filter` option will have effect only on newly
  	 * added data. It will _not_ re-evaluate already included features.
  	 *
  	 * @option coordsToLatLng: Function = *
  	 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
  	 * The default is the `coordsToLatLng` static method.
  	 *
  	 * @option markersInheritOptions: Boolean = false
  	 * Whether default Markers for "Point" type Features inherit from group options.
  	 */

  	initialize: function (geojson, options) {
  		setOptions(this, options);

  		this._layers = {};

  		if (geojson) {
  			this.addData(geojson);
  		}
  	},

  	// @method addData( <GeoJSON> data ): this
  	// Adds a GeoJSON object to the layer.
  	addData: function (geojson) {
  		var features = isArray(geojson) ? geojson : geojson.features,
  		    i, len, feature;

  		if (features) {
  			for (i = 0, len = features.length; i < len; i++) {
  				// only add this if geometry or geometries are set and not null
  				feature = features[i];
  				if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
  					this.addData(feature);
  				}
  			}
  			return this;
  		}

  		var options = this.options;

  		if (options.filter && !options.filter(geojson)) { return this; }

  		var layer = geometryToLayer(geojson, options);
  		if (!layer) {
  			return this;
  		}
  		layer.feature = asFeature(geojson);

  		layer.defaultOptions = layer.options;
  		this.resetStyle(layer);

  		if (options.onEachFeature) {
  			options.onEachFeature(geojson, layer);
  		}

  		return this.addLayer(layer);
  	},

  	// @method resetStyle( <Path> layer? ): this
  	// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
  	// If `layer` is omitted, the style of all features in the current layer is reset.
  	resetStyle: function (layer) {
  		if (layer === undefined) {
  			return this.eachLayer(this.resetStyle, this);
  		}
  		// reset any custom styles
  		layer.options = extend({}, layer.defaultOptions);
  		this._setLayerStyle(layer, this.options.style);
  		return this;
  	},

  	// @method setStyle( <Function> style ): this
  	// Changes styles of GeoJSON vector layers with the given style function.
  	setStyle: function (style) {
  		return this.eachLayer(function (layer) {
  			this._setLayerStyle(layer, style);
  		}, this);
  	},

  	_setLayerStyle: function (layer, style) {
  		if (layer.setStyle) {
  			if (typeof style === 'function') {
  				style = style(layer.feature);
  			}
  			layer.setStyle(style);
  		}
  	}
  });

  // @section
  // There are several static functions which can be called without instantiating L.GeoJSON:

  // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
  // Creates a `Layer` from a given GeoJSON feature. Can use a custom
  // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
  // functions if provided as options.
  function geometryToLayer(geojson, options) {

  	var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
  	    coords = geometry ? geometry.coordinates : null,
  	    layers = [],
  	    pointToLayer = options && options.pointToLayer,
  	    _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
  	    latlng, latlngs, i, len;

  	if (!coords && !geometry) {
  		return null;
  	}

  	switch (geometry.type) {
  	case 'Point':
  		latlng = _coordsToLatLng(coords);
  		return _pointToLayer(pointToLayer, geojson, latlng, options);

  	case 'MultiPoint':
  		for (i = 0, len = coords.length; i < len; i++) {
  			latlng = _coordsToLatLng(coords[i]);
  			layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
  		}
  		return new FeatureGroup(layers);

  	case 'LineString':
  	case 'MultiLineString':
  		latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
  		return new Polyline(latlngs, options);

  	case 'Polygon':
  	case 'MultiPolygon':
  		latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
  		return new Polygon(latlngs, options);

  	case 'GeometryCollection':
  		for (i = 0, len = geometry.geometries.length; i < len; i++) {
  			var layer = geometryToLayer({
  				geometry: geometry.geometries[i],
  				type: 'Feature',
  				properties: geojson.properties
  			}, options);

  			if (layer) {
  				layers.push(layer);
  			}
  		}
  		return new FeatureGroup(layers);

  	default:
  		throw new Error('Invalid GeoJSON object.');
  	}
  }

  function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
  	return pointToLayerFn ?
  		pointToLayerFn(geojson, latlng) :
  		new Marker(latlng, options && options.markersInheritOptions && options);
  }

  // @function coordsToLatLng(coords: Array): LatLng
  // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
  // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
  function coordsToLatLng(coords) {
  	return new LatLng(coords[1], coords[0], coords[2]);
  }

  // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
  // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
  // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
  // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
  function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
  	var latlngs = [];

  	for (var i = 0, len = coords.length, latlng; i < len; i++) {
  		latlng = levelsDeep ?
  			coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
  			(_coordsToLatLng || coordsToLatLng)(coords[i]);

  		latlngs.push(latlng);
  	}

  	return latlngs;
  }

  // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
  // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
  function latLngToCoords(latlng, precision) {
  	latlng = toLatLng(latlng);
  	return latlng.alt !== undefined ?
  		[formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
  		[formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
  }

  // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
  // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
  // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
  function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
  	var coords = [];

  	for (var i = 0, len = latlngs.length; i < len; i++) {
  		coords.push(levelsDeep ?
  			latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
  			latLngToCoords(latlngs[i], precision));
  	}

  	if (!levelsDeep && closed) {
  		coords.push(coords[0]);
  	}

  	return coords;
  }

  function getFeature(layer, newGeometry) {
  	return layer.feature ?
  		extend({}, layer.feature, {geometry: newGeometry}) :
  		asFeature(newGeometry);
  }

  // @function asFeature(geojson: Object): Object
  // Normalize GeoJSON geometries/features into GeoJSON features.
  function asFeature(geojson) {
  	if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
  		return geojson;
  	}

  	return {
  		type: 'Feature',
  		properties: {},
  		geometry: geojson
  	};
  }

  var PointToGeoJSON = {
  	toGeoJSON: function (precision) {
  		return getFeature(this, {
  			type: 'Point',
  			coordinates: latLngToCoords(this.getLatLng(), precision)
  		});
  	}
  };

  // @namespace Marker
  // @section Other methods
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
  Marker.include(PointToGeoJSON);

  // @namespace CircleMarker
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
  Circle.include(PointToGeoJSON);
  CircleMarker.include(PointToGeoJSON);


  // @namespace Polyline
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
  Polyline.include({
  	toGeoJSON: function (precision) {
  		var multi = !isFlat(this._latlngs);

  		var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);

  		return getFeature(this, {
  			type: (multi ? 'Multi' : '') + 'LineString',
  			coordinates: coords
  		});
  	}
  });

  // @namespace Polygon
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
  Polygon.include({
  	toGeoJSON: function (precision) {
  		var holes = !isFlat(this._latlngs),
  		    multi = holes && !isFlat(this._latlngs[0]);

  		var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);

  		if (!holes) {
  			coords = [coords];
  		}

  		return getFeature(this, {
  			type: (multi ? 'Multi' : '') + 'Polygon',
  			coordinates: coords
  		});
  	}
  });


  // @namespace LayerGroup
  LayerGroup.include({
  	toMultiPoint: function (precision) {
  		var coords = [];

  		this.eachLayer(function (layer) {
  			coords.push(layer.toGeoJSON(precision).geometry.coordinates);
  		});

  		return getFeature(this, {
  			type: 'MultiPoint',
  			coordinates: coords
  		});
  	},

  	// @method toGeoJSON(precision?: Number|false): Object
  	// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  	// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
  	toGeoJSON: function (precision) {

  		var type = this.feature && this.feature.geometry && this.feature.geometry.type;

  		if (type === 'MultiPoint') {
  			return this.toMultiPoint(precision);
  		}

  		var isGeometryCollection = type === 'GeometryCollection',
  		    jsons = [];

  		this.eachLayer(function (layer) {
  			if (layer.toGeoJSON) {
  				var json = layer.toGeoJSON(precision);
  				if (isGeometryCollection) {
  					jsons.push(json.geometry);
  				} else {
  					var feature = asFeature(json);
  					// Squash nested feature collections
  					if (feature.type === 'FeatureCollection') {
  						jsons.push.apply(jsons, feature.features);
  					} else {
  						jsons.push(feature);
  					}
  				}
  			}
  		});

  		if (isGeometryCollection) {
  			return getFeature(this, {
  				geometries: jsons,
  				type: 'GeometryCollection'
  			});
  		}

  		return {
  			type: 'FeatureCollection',
  			features: jsons
  		};
  	}
  });

  // @namespace GeoJSON
  // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
  // Creates a GeoJSON layer. Optionally accepts an object in
  // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
  // (you can alternatively add it later with `addData` method) and an `options` object.
  function geoJSON(geojson, options) {
  	return new GeoJSON(geojson, options);
  }

  // Backward compatibility.
  var geoJson = geoJSON;

  /*
   * @class ImageOverlay
   * @aka L.ImageOverlay
   * @inherits Interactive layer
   *
   * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
   *
   * @example
   *
   * ```js
   * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
   * 	imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
   * L.imageOverlay(imageUrl, imageBounds).addTo(map);
   * ```
   */

  var ImageOverlay = Layer.extend({

  	// @section
  	// @aka ImageOverlay options
  	options: {
  		// @option opacity: Number = 1.0
  		// The opacity of the image overlay.
  		opacity: 1,

  		// @option alt: String = ''
  		// Text for the `alt` attribute of the image (useful for accessibility).
  		alt: '',

  		// @option interactive: Boolean = false
  		// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
  		interactive: false,

  		// @option crossOrigin: Boolean|String = false
  		// Whether the crossOrigin attribute will be added to the image.
  		// If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
  		crossOrigin: false,

  		// @option errorOverlayUrl: String = ''
  		// URL to the overlay image to show in place of the overlay that failed to load.
  		errorOverlayUrl: '',

  		// @option zIndex: Number = 1
  		// The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
  		zIndex: 1,

  		// @option className: String = ''
  		// A custom class name to assign to the image. Empty by default.
  		className: ''
  	},

  	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
  		this._url = url;
  		this._bounds = toLatLngBounds(bounds);

  		setOptions(this, options);
  	},

  	onAdd: function () {
  		if (!this._image) {
  			this._initImage();

  			if (this.options.opacity < 1) {
  				this._updateOpacity();
  			}
  		}

  		if (this.options.interactive) {
  			addClass(this._image, 'leaflet-interactive');
  			this.addInteractiveTarget(this._image);
  		}

  		this.getPane().appendChild(this._image);
  		this._reset();
  	},

  	onRemove: function () {
  		remove(this._image);
  		if (this.options.interactive) {
  			this.removeInteractiveTarget(this._image);
  		}
  	},

  	// @method setOpacity(opacity: Number): this
  	// Sets the opacity of the overlay.
  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;

  		if (this._image) {
  			this._updateOpacity();
  		}
  		return this;
  	},

  	setStyle: function (styleOpts) {
  		if (styleOpts.opacity) {
  			this.setOpacity(styleOpts.opacity);
  		}
  		return this;
  	},

  	// @method bringToFront(): this
  	// Brings the layer to the top of all overlays.
  	bringToFront: function () {
  		if (this._map) {
  			toFront(this._image);
  		}
  		return this;
  	},

  	// @method bringToBack(): this
  	// Brings the layer to the bottom of all overlays.
  	bringToBack: function () {
  		if (this._map) {
  			toBack(this._image);
  		}
  		return this;
  	},

  	// @method setUrl(url: String): this
  	// Changes the URL of the image.
  	setUrl: function (url) {
  		this._url = url;

  		if (this._image) {
  			this._image.src = url;
  		}
  		return this;
  	},

  	// @method setBounds(bounds: LatLngBounds): this
  	// Update the bounds that this ImageOverlay covers
  	setBounds: function (bounds) {
  		this._bounds = toLatLngBounds(bounds);

  		if (this._map) {
  			this._reset();
  		}
  		return this;
  	},

  	getEvents: function () {
  		var events = {
  			zoom: this._reset,
  			viewreset: this._reset
  		};

  		if (this._zoomAnimated) {
  			events.zoomanim = this._animateZoom;
  		}

  		return events;
  	},

  	// @method setZIndex(value: Number): this
  	// Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
  	setZIndex: function (value) {
  		this.options.zIndex = value;
  		this._updateZIndex();
  		return this;
  	},

  	// @method getBounds(): LatLngBounds
  	// Get the bounds that this ImageOverlay covers
  	getBounds: function () {
  		return this._bounds;
  	},

  	// @method getElement(): HTMLElement
  	// Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
  	// used by this overlay.
  	getElement: function () {
  		return this._image;
  	},

  	_initImage: function () {
  		var wasElementSupplied = this._url.tagName === 'IMG';
  		var img = this._image = wasElementSupplied ? this._url : create$1('img');

  		addClass(img, 'leaflet-image-layer');
  		if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
  		if (this.options.className) { addClass(img, this.options.className); }

  		img.onselectstart = falseFn;
  		img.onmousemove = falseFn;

  		// @event load: Event
  		// Fired when the ImageOverlay layer has loaded its image
  		img.onload = bind(this.fire, this, 'load');
  		img.onerror = bind(this._overlayOnError, this, 'error');

  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
  			img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
  		}

  		if (this.options.zIndex) {
  			this._updateZIndex();
  		}

  		if (wasElementSupplied) {
  			this._url = img.src;
  			return;
  		}

  		img.src = this._url;
  		img.alt = this.options.alt;
  	},

  	_animateZoom: function (e) {
  		var scale = this._map.getZoomScale(e.zoom),
  		    offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;

  		setTransform(this._image, offset, scale);
  	},

  	_reset: function () {
  		var image = this._image,
  		    bounds = new Bounds(
  		        this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
  		        this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
  		    size = bounds.getSize();

  		setPosition(image, bounds.min);

  		image.style.width  = size.x + 'px';
  		image.style.height = size.y + 'px';
  	},

  	_updateOpacity: function () {
  		setOpacity(this._image, this.options.opacity);
  	},

  	_updateZIndex: function () {
  		if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
  			this._image.style.zIndex = this.options.zIndex;
  		}
  	},

  	_overlayOnError: function () {
  		// @event error: Event
  		// Fired when the ImageOverlay layer fails to load its image
  		this.fire('error');

  		var errorUrl = this.options.errorOverlayUrl;
  		if (errorUrl && this._url !== errorUrl) {
  			this._url = errorUrl;
  			this._image.src = errorUrl;
  		}
  	},

  	// @method getCenter(): LatLng
  	// Returns the center of the ImageOverlay.
  	getCenter: function () {
  		return this._bounds.getCenter();
  	}
  });

  // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
  // Instantiates an image overlay object given the URL of the image and the
  // geographical bounds it is tied to.
  var imageOverlay = function (url, bounds, options) {
  	return new ImageOverlay(url, bounds, options);
  };

  /*
   * @class VideoOverlay
   * @aka L.VideoOverlay
   * @inherits ImageOverlay
   *
   * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
   *
   * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
   * HTML5 element.
   *
   * @example
   *
   * ```js
   * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
   * 	videoBounds = [[ 32, -130], [ 13, -100]];
   * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
   * ```
   */

  var VideoOverlay = ImageOverlay.extend({

  	// @section
  	// @aka VideoOverlay options
  	options: {
  		// @option autoplay: Boolean = true
  		// Whether the video starts playing automatically when loaded.
  		// On some browsers autoplay will only work with `muted: true`
  		autoplay: true,

  		// @option loop: Boolean = true
  		// Whether the video will loop back to the beginning when played.
  		loop: true,

  		// @option keepAspectRatio: Boolean = true
  		// Whether the video will save aspect ratio after the projection.
  		// Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
  		keepAspectRatio: true,

  		// @option muted: Boolean = false
  		// Whether the video starts on mute when loaded.
  		muted: false,

  		// @option playsInline: Boolean = true
  		// Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
  		playsInline: true
  	},

  	_initImage: function () {
  		var wasElementSupplied = this._url.tagName === 'VIDEO';
  		var vid = this._image = wasElementSupplied ? this._url : create$1('video');

  		addClass(vid, 'leaflet-image-layer');
  		if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
  		if (this.options.className) { addClass(vid, this.options.className); }

  		vid.onselectstart = falseFn;
  		vid.onmousemove = falseFn;

  		// @event load: Event
  		// Fired when the video has finished loading the first frame
  		vid.onloadeddata = bind(this.fire, this, 'load');

  		if (wasElementSupplied) {
  			var sourceElements = vid.getElementsByTagName('source');
  			var sources = [];
  			for (var j = 0; j < sourceElements.length; j++) {
  				sources.push(sourceElements[j].src);
  			}

  			this._url = (sourceElements.length > 0) ? sources : [vid.src];
  			return;
  		}

  		if (!isArray(this._url)) { this._url = [this._url]; }

  		if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
  			vid.style['objectFit'] = 'fill';
  		}
  		vid.autoplay = !!this.options.autoplay;
  		vid.loop = !!this.options.loop;
  		vid.muted = !!this.options.muted;
  		vid.playsInline = !!this.options.playsInline;
  		for (var i = 0; i < this._url.length; i++) {
  			var source = create$1('source');
  			source.src = this._url[i];
  			vid.appendChild(source);
  		}
  	}

  	// @method getElement(): HTMLVideoElement
  	// Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
  	// used by this overlay.
  });


  // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
  // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
  // geographical bounds it is tied to.

  function videoOverlay(video, bounds, options) {
  	return new VideoOverlay(video, bounds, options);
  }

  /*
   * @class SVGOverlay
   * @aka L.SVGOverlay
   * @inherits ImageOverlay
   *
   * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
   *
   * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
   *
   * @example
   *
   * ```js
   * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
   * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
   * svgElement.setAttribute('viewBox', "0 0 200 200");
   * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
   * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
   * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
   * ```
   */

  var SVGOverlay = ImageOverlay.extend({
  	_initImage: function () {
  		var el = this._image = this._url;

  		addClass(el, 'leaflet-image-layer');
  		if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
  		if (this.options.className) { addClass(el, this.options.className); }

  		el.onselectstart = falseFn;
  		el.onmousemove = falseFn;
  	}

  	// @method getElement(): SVGElement
  	// Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
  	// used by this overlay.
  });


  // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
  // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
  // A viewBox attribute is required on the SVG element to zoom in and out properly.

  function svgOverlay(el, bounds, options) {
  	return new SVGOverlay(el, bounds, options);
  }

  /*
   * @class DivOverlay
   * @inherits Interactive layer
   * @aka L.DivOverlay
   * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
   */

  // @namespace DivOverlay
  var DivOverlay = Layer.extend({

  	// @section
  	// @aka DivOverlay options
  	options: {
  		// @option interactive: Boolean = false
  		// If true, the popup/tooltip will listen to the mouse events.
  		interactive: false,

  		// @option offset: Point = Point(0, 0)
  		// The offset of the overlay position.
  		offset: [0, 0],

  		// @option className: String = ''
  		// A custom CSS class name to assign to the overlay.
  		className: '',

  		// @option pane: String = undefined
  		// `Map pane` where the overlay will be added.
  		pane: undefined
  	},

  	initialize: function (options, source) {
  		setOptions(this, options);

  		this._source = source;
  	},

  	// @method openOn(map: Map): this
  	// Adds the overlay to the map.
  	// Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
  	openOn: function (map) {
  		map = arguments.length ? map : this._source._map; // experimental, not the part of public api
  		if (!map.hasLayer(this)) {
  			map.addLayer(this);
  		}
  		return this;
  	},

  	// @method close(): this
  	// Closes the overlay.
  	// Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
  	// and `layer.closePopup()`/`.closeTooltip()`.
  	close: function () {
  		if (this._map) {
  			this._map.removeLayer(this);
  		}
  		return this;
  	},

  	// @method toggle(layer?: Layer): this
  	// Opens or closes the overlay bound to layer depending on its current state.
  	// Argument may be omitted only for overlay bound to layer.
  	// Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
  	toggle: function (layer) {
  		if (this._map) {
  			this.close();
  		} else {
  			if (arguments.length) {
  				this._source = layer;
  			} else {
  				layer = this._source;
  			}
  			this._prepareOpen();

  			// open the overlay on the map
  			this.openOn(layer._map);
  		}
  		return this;
  	},

  	onAdd: function (map) {
  		this._zoomAnimated = map._zoomAnimated;

  		if (!this._container) {
  			this._initLayout();
  		}

  		if (map._fadeAnimated) {
  			setOpacity(this._container, 0);
  		}

  		clearTimeout(this._removeTimeout);
  		this.getPane().appendChild(this._container);
  		this.update();

  		if (map._fadeAnimated) {
  			setOpacity(this._container, 1);
  		}

  		this.bringToFront();

  		if (this.options.interactive) {
  			addClass(this._container, 'leaflet-interactive');
  			this.addInteractiveTarget(this._container);
  		}
  	},

  	onRemove: function (map) {
  		if (map._fadeAnimated) {
  			setOpacity(this._container, 0);
  			this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
  		} else {
  			remove(this._container);
  		}

  		if (this.options.interactive) {
  			removeClass(this._container, 'leaflet-interactive');
  			this.removeInteractiveTarget(this._container);
  		}
  	},

  	// @namespace DivOverlay
  	// @method getLatLng: LatLng
  	// Returns the geographical point of the overlay.
  	getLatLng: function () {
  		return this._latlng;
  	},

  	// @method setLatLng(latlng: LatLng): this
  	// Sets the geographical point where the overlay will open.
  	setLatLng: function (latlng) {
  		this._latlng = toLatLng(latlng);
  		if (this._map) {
  			this._updatePosition();
  			this._adjustPan();
  		}
  		return this;
  	},

  	// @method getContent: String|HTMLElement
  	// Returns the content of the overlay.
  	getContent: function () {
  		return this._content;
  	},

  	// @method setContent(htmlContent: String|HTMLElement|Function): this
  	// Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
  	// The function should return a `String` or `HTMLElement` to be used in the overlay.
  	setContent: function (content) {
  		this._content = content;
  		this.update();
  		return this;
  	},

  	// @method getElement: String|HTMLElement
  	// Returns the HTML container of the overlay.
  	getElement: function () {
  		return this._container;
  	},

  	// @method update: null
  	// Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
  	update: function () {
  		if (!this._map) { return; }

  		this._container.style.visibility = 'hidden';

  		this._updateContent();
  		this._updateLayout();
  		this._updatePosition();

  		this._container.style.visibility = '';

  		this._adjustPan();
  	},

  	getEvents: function () {
  		var events = {
  			zoom: this._updatePosition,
  			viewreset: this._updatePosition
  		};

  		if (this._zoomAnimated) {
  			events.zoomanim = this._animateZoom;
  		}
  		return events;
  	},

  	// @method isOpen: Boolean
  	// Returns `true` when the overlay is visible on the map.
  	isOpen: function () {
  		return !!this._map && this._map.hasLayer(this);
  	},

  	// @method bringToFront: this
  	// Brings this overlay in front of other overlays (in the same map pane).
  	bringToFront: function () {
  		if (this._map) {
  			toFront(this._container);
  		}
  		return this;
  	},

  	// @method bringToBack: this
  	// Brings this overlay to the back of other overlays (in the same map pane).
  	bringToBack: function () {
  		if (this._map) {
  			toBack(this._container);
  		}
  		return this;
  	},

  	// prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
  	_prepareOpen: function (latlng) {
  		var source = this._source;
  		if (!source._map) { return false; }

  		if (source instanceof FeatureGroup) {
  			source = null;
  			var layers = this._source._layers;
  			for (var id in layers) {
  				if (layers[id]._map) {
  					source = layers[id];
  					break;
  				}
  			}
  			if (!source) { return false; } // Unable to get source layer.

  			// set overlay source to this layer
  			this._source = source;
  		}

  		if (!latlng) {
  			if (source.getCenter) {
  				latlng = source.getCenter();
  			} else if (source.getLatLng) {
  				latlng = source.getLatLng();
  			} else if (source.getBounds) {
  				latlng = source.getBounds().getCenter();
  			} else {
  				throw new Error('Unable to get source layer LatLng.');
  			}
  		}
  		this.setLatLng(latlng);

  		if (this._map) {
  			// update the overlay (content, layout, etc...)
  			this.update();
  		}

  		return true;
  	},

  	_updateContent: function () {
  		if (!this._content) { return; }

  		var node = this._contentNode;
  		var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;

  		if (typeof content === 'string') {
  			node.innerHTML = content;
  		} else {
  			while (node.hasChildNodes()) {
  				node.removeChild(node.firstChild);
  			}
  			node.appendChild(content);
  		}

  		// @namespace DivOverlay
  		// @section DivOverlay events
  		// @event contentupdate: Event
  		// Fired when the content of the overlay is updated
  		this.fire('contentupdate');
  	},

  	_updatePosition: function () {
  		if (!this._map) { return; }

  		var pos = this._map.latLngToLayerPoint(this._latlng),
  		    offset = toPoint(this.options.offset),
  		    anchor = this._getAnchor();

  		if (this._zoomAnimated) {
  			setPosition(this._container, pos.add(anchor));
  		} else {
  			offset = offset.add(pos).add(anchor);
  		}

  		var bottom = this._containerBottom = -offset.y,
  		    left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;

  		// bottom position the overlay in case the height of the overlay changes (images loading etc)
  		this._container.style.bottom = bottom + 'px';
  		this._container.style.left = left + 'px';
  	},

  	_getAnchor: function () {
  		return [0, 0];
  	}

  });

  Map.include({
  	_initOverlay: function (OverlayClass, content, latlng, options) {
  		var overlay = content;
  		if (!(overlay instanceof OverlayClass)) {
  			overlay = new OverlayClass(options).setContent(content);
  		}
  		if (latlng) {
  			overlay.setLatLng(latlng);
  		}
  		return overlay;
  	}
  });


  Layer.include({
  	_initOverlay: function (OverlayClass, old, content, options) {
  		var overlay = content;
  		if (overlay instanceof OverlayClass) {
  			setOptions(overlay, options);
  			overlay._source = this;
  		} else {
  			overlay = (old && !options) ? old : new OverlayClass(options, this);
  			overlay.setContent(content);
  		}
  		return overlay;
  	}
  });

  /*
   * @class Popup
   * @inherits DivOverlay
   * @aka L.Popup
   * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
   * open popups while making sure that only one popup is open at one time
   * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
   *
   * @example
   *
   * If you want to just bind a popup to marker click and then open it, it's really easy:
   *
   * ```js
   * marker.bindPopup(popupContent).openPopup();
   * ```
   * Path overlays like polylines also have a `bindPopup` method.
   * Here's a more complicated way to open a popup on a map:
   *
   * ```js
   * var popup = L.popup()
   * 	.setLatLng(latlng)
   * 	.setContent('<p>Hello world!<br />This is a nice popup.</p>')
   * 	.openOn(map);
   * ```
   */


  // @namespace Popup
  var Popup = DivOverlay.extend({

  	// @section
  	// @aka Popup options
  	options: {
  		// @option pane: String = 'popupPane'
  		// `Map pane` where the popup will be added.
  		pane: 'popupPane',

  		// @option offset: Point = Point(0, 7)
  		// The offset of the popup position.
  		offset: [0, 7],

  		// @option maxWidth: Number = 300
  		// Max width of the popup, in pixels.
  		maxWidth: 300,

  		// @option minWidth: Number = 50
  		// Min width of the popup, in pixels.
  		minWidth: 50,

  		// @option maxHeight: Number = null
  		// If set, creates a scrollable container of the given height
  		// inside a popup if its content exceeds it.
  		maxHeight: null,

  		// @option autoPan: Boolean = true
  		// Set it to `false` if you don't want the map to do panning animation
  		// to fit the opened popup.
  		autoPan: true,

  		// @option autoPanPaddingTopLeft: Point = null
  		// The margin between the popup and the top left corner of the map
  		// view after autopanning was performed.
  		autoPanPaddingTopLeft: null,

  		// @option autoPanPaddingBottomRight: Point = null
  		// The margin between the popup and the bottom right corner of the map
  		// view after autopanning was performed.
  		autoPanPaddingBottomRight: null,

  		// @option autoPanPadding: Point = Point(5, 5)
  		// Equivalent of setting both top left and bottom right autopan padding to the same value.
  		autoPanPadding: [5, 5],

  		// @option keepInView: Boolean = false
  		// Set it to `true` if you want to prevent users from panning the popup
  		// off of the screen while it is open.
  		keepInView: false,

  		// @option closeButton: Boolean = true
  		// Controls the presence of a close button in the popup.
  		closeButton: true,

  		// @option autoClose: Boolean = true
  		// Set it to `false` if you want to override the default behavior of
  		// the popup closing when another popup is opened.
  		autoClose: true,

  		// @option closeOnEscapeKey: Boolean = true
  		// Set it to `false` if you want to override the default behavior of
  		// the ESC key for closing of the popup.
  		closeOnEscapeKey: true,

  		// @option closeOnClick: Boolean = *
  		// Set it if you want to override the default behavior of the popup closing when user clicks
  		// on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.

  		// @option className: String = ''
  		// A custom CSS class name to assign to the popup.
  		className: ''
  	},

  	// @namespace Popup
  	// @method openOn(map: Map): this
  	// Alternative to `map.openPopup(popup)`.
  	// Adds the popup to the map and closes the previous one.
  	openOn: function (map) {
  		map = arguments.length ? map : this._source._map; // experimental, not the part of public api

  		if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
  			map.removeLayer(map._popup);
  		}
  		map._popup = this;

  		return DivOverlay.prototype.openOn.call(this, map);
  	},

  	onAdd: function (map) {
  		DivOverlay.prototype.onAdd.call(this, map);

  		// @namespace Map
  		// @section Popup events
  		// @event popupopen: PopupEvent
  		// Fired when a popup is opened in the map
  		map.fire('popupopen', {popup: this});

  		if (this._source) {
  			// @namespace Layer
  			// @section Popup events
  			// @event popupopen: PopupEvent
  			// Fired when a popup bound to this layer is opened
  			this._source.fire('popupopen', {popup: this}, true);
  			// For non-path layers, we toggle the popup when clicking
  			// again the layer, so prevent the map to reopen it.
  			if (!(this._source instanceof Path)) {
  				this._source.on('preclick', stopPropagation);
  			}
  		}
  	},

  	onRemove: function (map) {
  		DivOverlay.prototype.onRemove.call(this, map);

  		// @namespace Map
  		// @section Popup events
  		// @event popupclose: PopupEvent
  		// Fired when a popup in the map is closed
  		map.fire('popupclose', {popup: this});

  		if (this._source) {
  			// @namespace Layer
  			// @section Popup events
  			// @event popupclose: PopupEvent
  			// Fired when a popup bound to this layer is closed
  			this._source.fire('popupclose', {popup: this}, true);
  			if (!(this._source instanceof Path)) {
  				this._source.off('preclick', stopPropagation);
  			}
  		}
  	},

  	getEvents: function () {
  		var events = DivOverlay.prototype.getEvents.call(this);

  		if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
  			events.preclick = this.close;
  		}

  		if (this.options.keepInView) {
  			events.moveend = this._adjustPan;
  		}

  		return events;
  	},

  	_initLayout: function () {
  		var prefix = 'leaflet-popup',
  		    container = this._container = create$1('div',
  			prefix + ' ' + (this.options.className || '') +
  			' leaflet-zoom-animated');

  		var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
  		this._contentNode = create$1('div', prefix + '-content', wrapper);

  		disableClickPropagation(container);
  		disableScrollPropagation(this._contentNode);
  		on(container, 'contextmenu', stopPropagation);

  		this._tipContainer = create$1('div', prefix + '-tip-container', container);
  		this._tip = create$1('div', prefix + '-tip', this._tipContainer);

  		if (this.options.closeButton) {
  			var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
  			closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
  			closeButton.setAttribute('aria-label', 'Close popup');
  			closeButton.href = '#close';
  			closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';

  			on(closeButton, 'click', this.close, this);
  		}
  	},

  	_updateLayout: function () {
  		var container = this._contentNode,
  		    style = container.style;

  		style.width = '';
  		style.whiteSpace = 'nowrap';

  		var width = container.offsetWidth;
  		width = Math.min(width, this.options.maxWidth);
  		width = Math.max(width, this.options.minWidth);

  		style.width = (width + 1) + 'px';
  		style.whiteSpace = '';

  		style.height = '';

  		var height = container.offsetHeight,
  		    maxHeight = this.options.maxHeight,
  		    scrolledClass = 'leaflet-popup-scrolled';

  		if (maxHeight && height > maxHeight) {
  			style.height = maxHeight + 'px';
  			addClass(container, scrolledClass);
  		} else {
  			removeClass(container, scrolledClass);
  		}

  		this._containerWidth = this._container.offsetWidth;
  	},

  	_animateZoom: function (e) {
  		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
  		    anchor = this._getAnchor();
  		setPosition(this._container, pos.add(anchor));
  	},

  	_adjustPan: function (e) {
  		if (!this.options.autoPan) { return; }
  		if (this._map._panAnim) { this._map._panAnim.stop(); }

  		var map = this._map,
  		    marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
  		    containerHeight = this._container.offsetHeight + marginBottom,
  		    containerWidth = this._containerWidth,
  		    layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);

  		layerPos._add(getPosition(this._container));

  		var containerPos = map.layerPointToContainerPoint(layerPos),
  		    padding = toPoint(this.options.autoPanPadding),
  		    paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
  		    paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
  		    size = map.getSize(),
  		    dx = 0,
  		    dy = 0;

  		if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
  			dx = containerPos.x + containerWidth - size.x + paddingBR.x;
  		}
  		if (containerPos.x - dx - paddingTL.x < 0) { // left
  			dx = containerPos.x - paddingTL.x;
  		}
  		if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
  			dy = containerPos.y + containerHeight - size.y + paddingBR.y;
  		}
  		if (containerPos.y - dy - paddingTL.y < 0) { // top
  			dy = containerPos.y - paddingTL.y;
  		}

  		// @namespace Map
  		// @section Popup events
  		// @event autopanstart: Event
  		// Fired when the map starts autopanning when opening a popup.
  		if (dx || dy) {
  			map
  			    .fire('autopanstart')
  			    .panBy([dx, dy], {animate: e && e.type === 'moveend'});
  		}
  	},

  	_getAnchor: function () {
  		// Where should we anchor the popup on the source layer?
  		return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
  	}

  });

  // @namespace Popup
  // @factory L.popup(options?: Popup options, source?: Layer)
  // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
  var popup = function (options, source) {
  	return new Popup(options, source);
  };


  /* @namespace Map
   * @section Interaction Options
   * @option closePopupOnClick: Boolean = true
   * Set it to `false` if you don't want popups to close when user clicks the map.
   */
  Map.mergeOptions({
  	closePopupOnClick: true
  });


  // @namespace Map
  // @section Methods for Layers and Controls
  Map.include({
  	// @method openPopup(popup: Popup): this
  	// Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
  	// @alternative
  	// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
  	// Creates a popup with the specified content and options and opens it in the given point on a map.
  	openPopup: function (popup, latlng, options) {
  		this._initOverlay(Popup, popup, latlng, options)
  		  .openOn(this);

  		return this;
  	},

  	// @method closePopup(popup?: Popup): this
  	// Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
  	closePopup: function (popup) {
  		popup = arguments.length ? popup : this._popup;
  		if (popup) {
  			popup.close();
  		}
  		return this;
  	}
  });

  /*
   * @namespace Layer
   * @section Popup methods example
   *
   * All layers share a set of methods convenient for binding popups to it.
   *
   * ```js
   * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
   * layer.openPopup();
   * layer.closePopup();
   * ```
   *
   * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
   */

  // @section Popup methods
  Layer.include({

  	// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
  	// Binds a popup to the layer with the passed `content` and sets up the
  	// necessary event listeners. If a `Function` is passed it will receive
  	// the layer as the first argument and should return a `String` or `HTMLElement`.
  	bindPopup: function (content, options) {
  		this._popup = this._initOverlay(Popup, this._popup, content, options);
  		if (!this._popupHandlersAdded) {
  			this.on({
  				click: this._openPopup,
  				keypress: this._onKeyPress,
  				remove: this.closePopup,
  				move: this._movePopup
  			});
  			this._popupHandlersAdded = true;
  		}

  		return this;
  	},

  	// @method unbindPopup(): this
  	// Removes the popup previously bound with `bindPopup`.
  	unbindPopup: function () {
  		if (this._popup) {
  			this.off({
  				click: this._openPopup,
  				keypress: this._onKeyPress,
  				remove: this.closePopup,
  				move: this._movePopup
  			});
  			this._popupHandlersAdded = false;
  			this._popup = null;
  		}
  		return this;
  	},

  	// @method openPopup(latlng?: LatLng): this
  	// Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
  	openPopup: function (latlng) {
  		if (this._popup && this._popup._prepareOpen(latlng)) {
  			// open the popup on the map
  			this._popup.openOn(this._map);
  		}
  		return this;
  	},

  	// @method closePopup(): this
  	// Closes the popup bound to this layer if it is open.
  	closePopup: function () {
  		if (this._popup) {
  			this._popup.close();
  		}
  		return this;
  	},

  	// @method togglePopup(): this
  	// Opens or closes the popup bound to this layer depending on its current state.
  	togglePopup: function () {
  		if (this._popup) {
  			this._popup.toggle(this);
  		}
  		return this;
  	},

  	// @method isPopupOpen(): boolean
  	// Returns `true` if the popup bound to this layer is currently open.
  	isPopupOpen: function () {
  		return (this._popup ? this._popup.isOpen() : false);
  	},

  	// @method setPopupContent(content: String|HTMLElement|Popup): this
  	// Sets the content of the popup bound to this layer.
  	setPopupContent: function (content) {
  		if (this._popup) {
  			this._popup.setContent(content);
  		}
  		return this;
  	},

  	// @method getPopup(): Popup
  	// Returns the popup bound to this layer.
  	getPopup: function () {
  		return this._popup;
  	},

  	_openPopup: function (e) {
  		if (!this._popup || !this._map) {
  			return;
  		}
  		// prevent map click
  		stop(e);

  		var target = e.layer || e.target;
  		if (this._popup._source === target && !(target instanceof Path)) {
  			// treat it like a marker and figure out
  			// if we should toggle it open/closed
  			if (this._map.hasLayer(this._popup)) {
  				this.closePopup();
  			} else {
  				this.openPopup(e.latlng);
  			}
  			return;
  		}
  		this._popup._source = target;
  		this.openPopup(e.latlng);
  	},

  	_movePopup: function (e) {
  		this._popup.setLatLng(e.latlng);
  	},

  	_onKeyPress: function (e) {
  		if (e.originalEvent.keyCode === 13) {
  			this._openPopup(e);
  		}
  	}
  });

  /*
   * @class Tooltip
   * @inherits DivOverlay
   * @aka L.Tooltip
   * Used to display small texts on top of map layers.
   *
   * @example
   *
   * ```js
   * marker.bindTooltip("my tooltip text").openTooltip();
   * ```
   * Note about tooltip offset. Leaflet takes two options in consideration
   * for computing tooltip offsetting:
   * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
   *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
   *   move it to the bottom. Negatives will move to the left and top.
   * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
   *   should adapt this value if you use a custom icon.
   */


  // @namespace Tooltip
  var Tooltip = DivOverlay.extend({

  	// @section
  	// @aka Tooltip options
  	options: {
  		// @option pane: String = 'tooltipPane'
  		// `Map pane` where the tooltip will be added.
  		pane: 'tooltipPane',

  		// @option offset: Point = Point(0, 0)
  		// Optional offset of the tooltip position.
  		offset: [0, 0],

  		// @option direction: String = 'auto'
  		// Direction where to open the tooltip. Possible values are: `right`, `left`,
  		// `top`, `bottom`, `center`, `auto`.
  		// `auto` will dynamically switch between `right` and `left` according to the tooltip
  		// position on the map.
  		direction: 'auto',

  		// @option permanent: Boolean = false
  		// Whether to open the tooltip permanently or only on mouseover.
  		permanent: false,

  		// @option sticky: Boolean = false
  		// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
  		sticky: false,

  		// @option opacity: Number = 0.9
  		// Tooltip container opacity.
  		opacity: 0.9
  	},

  	onAdd: function (map) {
  		DivOverlay.prototype.onAdd.call(this, map);
  		this.setOpacity(this.options.opacity);

  		// @namespace Map
  		// @section Tooltip events
  		// @event tooltipopen: TooltipEvent
  		// Fired when a tooltip is opened in the map.
  		map.fire('tooltipopen', {tooltip: this});

  		if (this._source) {
  			this.addEventParent(this._source);

  			// @namespace Layer
  			// @section Tooltip events
  			// @event tooltipopen: TooltipEvent
  			// Fired when a tooltip bound to this layer is opened.
  			this._source.fire('tooltipopen', {tooltip: this}, true);
  		}
  	},

  	onRemove: function (map) {
  		DivOverlay.prototype.onRemove.call(this, map);

  		// @namespace Map
  		// @section Tooltip events
  		// @event tooltipclose: TooltipEvent
  		// Fired when a tooltip in the map is closed.
  		map.fire('tooltipclose', {tooltip: this});

  		if (this._source) {
  			this.removeEventParent(this._source);

  			// @namespace Layer
  			// @section Tooltip events
  			// @event tooltipclose: TooltipEvent
  			// Fired when a tooltip bound to this layer is closed.
  			this._source.fire('tooltipclose', {tooltip: this}, true);
  		}
  	},

  	getEvents: function () {
  		var events = DivOverlay.prototype.getEvents.call(this);

  		if (!this.options.permanent) {
  			events.preclick = this.close;
  		}

  		return events;
  	},

  	_initLayout: function () {
  		var prefix = 'leaflet-tooltip',
  		    className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

  		this._contentNode = this._container = create$1('div', className);
  	},

  	_updateLayout: function () {},

  	_adjustPan: function () {},

  	_setPosition: function (pos) {
  		var subX, subY,
  		    map = this._map,
  		    container = this._container,
  		    centerPoint = map.latLngToContainerPoint(map.getCenter()),
  		    tooltipPoint = map.layerPointToContainerPoint(pos),
  		    direction = this.options.direction,
  		    tooltipWidth = container.offsetWidth,
  		    tooltipHeight = container.offsetHeight,
  		    offset = toPoint(this.options.offset),
  		    anchor = this._getAnchor();

  		if (direction === 'top') {
  			subX = tooltipWidth / 2;
  			subY = tooltipHeight;
  		} else if (direction === 'bottom') {
  			subX = tooltipWidth / 2;
  			subY = 0;
  		} else if (direction === 'center') {
  			subX = tooltipWidth / 2;
  			subY = tooltipHeight / 2;
  		} else if (direction === 'right') {
  			subX = 0;
  			subY = tooltipHeight / 2;
  		} else if (direction === 'left') {
  			subX = tooltipWidth;
  			subY = tooltipHeight / 2;
  		} else if (tooltipPoint.x < centerPoint.x) {
  			direction = 'right';
  			subX = 0;
  			subY = tooltipHeight / 2;
  		} else {
  			direction = 'left';
  			subX = tooltipWidth + (offset.x + anchor.x) * 2;
  			subY = tooltipHeight / 2;
  		}

  		pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);

  		removeClass(container, 'leaflet-tooltip-right');
  		removeClass(container, 'leaflet-tooltip-left');
  		removeClass(container, 'leaflet-tooltip-top');
  		removeClass(container, 'leaflet-tooltip-bottom');
  		addClass(container, 'leaflet-tooltip-' + direction);
  		setPosition(container, pos);
  	},

  	_updatePosition: function () {
  		var pos = this._map.latLngToLayerPoint(this._latlng);
  		this._setPosition(pos);
  	},

  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;

  		if (this._container) {
  			setOpacity(this._container, opacity);
  		}
  	},

  	_animateZoom: function (e) {
  		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
  		this._setPosition(pos);
  	},

  	_getAnchor: function () {
  		// Where should we anchor the tooltip on the source layer?
  		return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
  	}

  });

  // @namespace Tooltip
  // @factory L.tooltip(options?: Tooltip options, source?: Layer)
  // Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
  var tooltip = function (options, source) {
  	return new Tooltip(options, source);
  };

  // @namespace Map
  // @section Methods for Layers and Controls
  Map.include({

  	// @method openTooltip(tooltip: Tooltip): this
  	// Opens the specified tooltip.
  	// @alternative
  	// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
  	// Creates a tooltip with the specified content and options and open it.
  	openTooltip: function (tooltip, latlng, options) {
  		this._initOverlay(Tooltip, tooltip, latlng, options)
  		  .openOn(this);

  		return this;
  	},

  	// @method closeTooltip(tooltip: Tooltip): this
  	// Closes the tooltip given as parameter.
  	closeTooltip: function (tooltip) {
  		tooltip.close();
  		return this;
  	}

  });

  /*
   * @namespace Layer
   * @section Tooltip methods example
   *
   * All layers share a set of methods convenient for binding tooltips to it.
   *
   * ```js
   * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
   * layer.openTooltip();
   * layer.closeTooltip();
   * ```
   */

  // @section Tooltip methods
  Layer.include({

  	// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
  	// Binds a tooltip to the layer with the passed `content` and sets up the
  	// necessary event listeners. If a `Function` is passed it will receive
  	// the layer as the first argument and should return a `String` or `HTMLElement`.
  	bindTooltip: function (content, options) {

  		if (this._tooltip && this.isTooltipOpen()) {
  			this.unbindTooltip();
  		}

  		this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
  		this._initTooltipInteractions();

  		if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
  			this.openTooltip();
  		}

  		return this;
  	},

  	// @method unbindTooltip(): this
  	// Removes the tooltip previously bound with `bindTooltip`.
  	unbindTooltip: function () {
  		if (this._tooltip) {
  			this._initTooltipInteractions(true);
  			this.closeTooltip();
  			this._tooltip = null;
  		}
  		return this;
  	},

  	_initTooltipInteractions: function (remove) {
  		if (!remove && this._tooltipHandlersAdded) { return; }
  		var onOff = remove ? 'off' : 'on',
  		    events = {
  			remove: this.closeTooltip,
  			move: this._moveTooltip
  		    };
  		if (!this._tooltip.options.permanent) {
  			events.mouseover = this._openTooltip;
  			events.mouseout = this.closeTooltip;
  			events.click = this._openTooltip;
  		} else {
  			events.add = this._openTooltip;
  		}
  		if (this._tooltip.options.sticky) {
  			events.mousemove = this._moveTooltip;
  		}
  		this[onOff](events);
  		this._tooltipHandlersAdded = !remove;
  	},

  	// @method openTooltip(latlng?: LatLng): this
  	// Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
  	openTooltip: function (latlng) {
  		if (this._tooltip && this._tooltip._prepareOpen(latlng)) {
  			// open the tooltip on the map
  			this._tooltip.openOn(this._map);
  		}
  		return this;
  	},

  	// @method closeTooltip(): this
  	// Closes the tooltip bound to this layer if it is open.
  	closeTooltip: function () {
  		if (this._tooltip) {
  			return this._tooltip.close();
  		}
  	},

  	// @method toggleTooltip(): this
  	// Opens or closes the tooltip bound to this layer depending on its current state.
  	toggleTooltip: function () {
  		if (this._tooltip) {
  			this._tooltip.toggle(this);
  		}
  		return this;
  	},

  	// @method isTooltipOpen(): boolean
  	// Returns `true` if the tooltip bound to this layer is currently open.
  	isTooltipOpen: function () {
  		return this._tooltip.isOpen();
  	},

  	// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
  	// Sets the content of the tooltip bound to this layer.
  	setTooltipContent: function (content) {
  		if (this._tooltip) {
  			this._tooltip.setContent(content);
  		}
  		return this;
  	},

  	// @method getTooltip(): Tooltip
  	// Returns the tooltip bound to this layer.
  	getTooltip: function () {
  		return this._tooltip;
  	},

  	_openTooltip: function (e) {
  		if (!this._tooltip || !this._map || (this._map.dragging && this._map.dragging.moving())) {
  			return;
  		}
  		this._tooltip._source = e.layer || e.target;

  		this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
  	},

  	_moveTooltip: function (e) {
  		var latlng = e.latlng, containerPoint, layerPoint;
  		if (this._tooltip.options.sticky && e.originalEvent) {
  			containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
  			layerPoint = this._map.containerPointToLayerPoint(containerPoint);
  			latlng = this._map.layerPointToLatLng(layerPoint);
  		}
  		this._tooltip.setLatLng(latlng);
  	}
  });

  /*
   * @class DivIcon
   * @aka L.DivIcon
   * @inherits Icon
   *
   * Represents a lightweight icon for markers that uses a simple `<div>`
   * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
   *
   * @example
   * ```js
   * var myIcon = L.divIcon({className: 'my-div-icon'});
   * // you can set .my-div-icon styles in CSS
   *
   * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
   * ```
   *
   * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
   */

  var DivIcon = Icon.extend({
  	options: {
  		// @section
  		// @aka DivIcon options
  		iconSize: [12, 12], // also can be set through CSS

  		// iconAnchor: (Point),
  		// popupAnchor: (Point),

  		// @option html: String|HTMLElement = ''
  		// Custom HTML code to put inside the div element, empty by default. Alternatively,
  		// an instance of `HTMLElement`.
  		html: false,

  		// @option bgPos: Point = [0, 0]
  		// Optional relative position of the background, in pixels
  		bgPos: null,

  		className: 'leaflet-div-icon'
  	},

  	createIcon: function (oldIcon) {
  		var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
  		    options = this.options;

  		if (options.html instanceof Element) {
  			empty(div);
  			div.appendChild(options.html);
  		} else {
  			div.innerHTML = options.html !== false ? options.html : '';
  		}

  		if (options.bgPos) {
  			var bgPos = toPoint(options.bgPos);
  			div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
  		}
  		this._setIconStyles(div, 'icon');

  		return div;
  	},

  	createShadow: function () {
  		return null;
  	}
  });

  // @factory L.divIcon(options: DivIcon options)
  // Creates a `DivIcon` instance with the given options.
  function divIcon(options) {
  	return new DivIcon(options);
  }

  Icon.Default = IconDefault;

  /*
   * @class GridLayer
   * @inherits Layer
   * @aka L.GridLayer
   *
   * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
   * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
   *
   *
   * @section Synchronous usage
   * @example
   *
   * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
   *
   * ```js
   * var CanvasLayer = L.GridLayer.extend({
   *     createTile: function(coords){
   *         // create a <canvas> element for drawing
   *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
   *
   *         // setup tile width and height according to the options
   *         var size = this.getTileSize();
   *         tile.width = size.x;
   *         tile.height = size.y;
   *
   *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
   *         var ctx = tile.getContext('2d');
   *
   *         // return the tile so it can be rendered on screen
   *         return tile;
   *     }
   * });
   * ```
   *
   * @section Asynchronous usage
   * @example
   *
   * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
   *
   * ```js
   * var CanvasLayer = L.GridLayer.extend({
   *     createTile: function(coords, done){
   *         var error;
   *
   *         // create a <canvas> element for drawing
   *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
   *
   *         // setup tile width and height according to the options
   *         var size = this.getTileSize();
   *         tile.width = size.x;
   *         tile.height = size.y;
   *
   *         // draw something asynchronously and pass the tile to the done() callback
   *         setTimeout(function() {
   *             done(error, tile);
   *         }, 1000);
   *
   *         return tile;
   *     }
   * });
   * ```
   *
   * @section
   */


  var GridLayer = Layer.extend({

  	// @section
  	// @aka GridLayer options
  	options: {
  		// @option tileSize: Number|Point = 256
  		// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
  		tileSize: 256,

  		// @option opacity: Number = 1.0
  		// Opacity of the tiles. Can be used in the `createTile()` function.
  		opacity: 1,

  		// @option updateWhenIdle: Boolean = (depends)
  		// Load new tiles only when panning ends.
  		// `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
  		// `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
  		// [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
  		updateWhenIdle: Browser.mobile,

  		// @option updateWhenZooming: Boolean = true
  		// By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
  		updateWhenZooming: true,

  		// @option updateInterval: Number = 200
  		// Tiles will not update more than once every `updateInterval` milliseconds when panning.
  		updateInterval: 200,

  		// @option zIndex: Number = 1
  		// The explicit zIndex of the tile layer.
  		zIndex: 1,

  		// @option bounds: LatLngBounds = undefined
  		// If set, tiles will only be loaded inside the set `LatLngBounds`.
  		bounds: null,

  		// @option minZoom: Number = 0
  		// The minimum zoom level down to which this layer will be displayed (inclusive).
  		minZoom: 0,

  		// @option maxZoom: Number = undefined
  		// The maximum zoom level up to which this layer will be displayed (inclusive).
  		maxZoom: undefined,

  		// @option maxNativeZoom: Number = undefined
  		// Maximum zoom number the tile source has available. If it is specified,
  		// the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
  		// from `maxNativeZoom` level and auto-scaled.
  		maxNativeZoom: undefined,

  		// @option minNativeZoom: Number = undefined
  		// Minimum zoom number the tile source has available. If it is specified,
  		// the tiles on all zoom levels lower than `minNativeZoom` will be loaded
  		// from `minNativeZoom` level and auto-scaled.
  		minNativeZoom: undefined,

  		// @option noWrap: Boolean = false
  		// Whether the layer is wrapped around the antimeridian. If `true`, the
  		// GridLayer will only be displayed once at low zoom levels. Has no
  		// effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
  		// in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
  		// tiles outside the CRS limits.
  		noWrap: false,

  		// @option pane: String = 'tilePane'
  		// `Map pane` where the grid layer will be added.
  		pane: 'tilePane',

  		// @option className: String = ''
  		// A custom class name to assign to the tile layer. Empty by default.
  		className: '',

  		// @option keepBuffer: Number = 2
  		// When panning the map, keep this many rows and columns of tiles before unloading them.
  		keepBuffer: 2
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  	},

  	onAdd: function () {
  		this._initContainer();

  		this._levels = {};
  		this._tiles = {};

  		this._resetView(); // implicit _update() call
  	},

  	beforeAdd: function (map) {
  		map._addZoomLimit(this);
  	},

  	onRemove: function (map) {
  		this._removeAllTiles();
  		remove(this._container);
  		map._removeZoomLimit(this);
  		this._container = null;
  		this._tileZoom = undefined;
  	},

  	// @method bringToFront: this
  	// Brings the tile layer to the top of all tile layers.
  	bringToFront: function () {
  		if (this._map) {
  			toFront(this._container);
  			this._setAutoZIndex(Math.max);
  		}
  		return this;
  	},

  	// @method bringToBack: this
  	// Brings the tile layer to the bottom of all tile layers.
  	bringToBack: function () {
  		if (this._map) {
  			toBack(this._container);
  			this._setAutoZIndex(Math.min);
  		}
  		return this;
  	},

  	// @method getContainer: HTMLElement
  	// Returns the HTML element that contains the tiles for this layer.
  	getContainer: function () {
  		return this._container;
  	},

  	// @method setOpacity(opacity: Number): this
  	// Changes the [opacity](#gridlayer-opacity) of the grid layer.
  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;
  		this._updateOpacity();
  		return this;
  	},

  	// @method setZIndex(zIndex: Number): this
  	// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
  	setZIndex: function (zIndex) {
  		this.options.zIndex = zIndex;
  		this._updateZIndex();

  		return this;
  	},

  	// @method isLoading: Boolean
  	// Returns `true` if any tile in the grid layer has not finished loading.
  	isLoading: function () {
  		return this._loading;
  	},

  	// @method redraw: this
  	// Causes the layer to clear all the tiles and request them again.
  	redraw: function () {
  		if (this._map) {
  			this._removeAllTiles();
  			var tileZoom = this._clampZoom(this._map.getZoom());
  			if (tileZoom !== this._tileZoom) {
  				this._tileZoom = tileZoom;
  				this._updateLevels();
  			}
  			this._update();
  		}
  		return this;
  	},

  	getEvents: function () {
  		var events = {
  			viewprereset: this._invalidateAll,
  			viewreset: this._resetView,
  			zoom: this._resetView,
  			moveend: this._onMoveEnd
  		};

  		if (!this.options.updateWhenIdle) {
  			// update tiles on move, but not more often than once per given interval
  			if (!this._onMove) {
  				this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
  			}

  			events.move = this._onMove;
  		}

  		if (this._zoomAnimated) {
  			events.zoomanim = this._animateZoom;
  		}

  		return events;
  	},

  	// @section Extension methods
  	// Layers extending `GridLayer` shall reimplement the following method.
  	// @method createTile(coords: Object, done?: Function): HTMLElement
  	// Called only internally, must be overridden by classes extending `GridLayer`.
  	// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
  	// is specified, it must be called when the tile has finished loading and drawing.
  	createTile: function () {
  		return document.createElement('div');
  	},

  	// @section
  	// @method getTileSize: Point
  	// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
  	getTileSize: function () {
  		var s = this.options.tileSize;
  		return s instanceof Point ? s : new Point(s, s);
  	},

  	_updateZIndex: function () {
  		if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
  			this._container.style.zIndex = this.options.zIndex;
  		}
  	},

  	_setAutoZIndex: function (compare) {
  		// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)

  		var layers = this.getPane().children,
  		    edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min

  		for (var i = 0, len = layers.length, zIndex; i < len; i++) {

  			zIndex = layers[i].style.zIndex;

  			if (layers[i] !== this._container && zIndex) {
  				edgeZIndex = compare(edgeZIndex, +zIndex);
  			}
  		}

  		if (isFinite(edgeZIndex)) {
  			this.options.zIndex = edgeZIndex + compare(-1, 1);
  			this._updateZIndex();
  		}
  	},

  	_updateOpacity: function () {
  		if (!this._map) { return; }

  		// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
  		if (Browser.ielt9) { return; }

  		setOpacity(this._container, this.options.opacity);

  		var now = +new Date(),
  		    nextFrame = false,
  		    willPrune = false;

  		for (var key in this._tiles) {
  			var tile = this._tiles[key];
  			if (!tile.current || !tile.loaded) { continue; }

  			var fade = Math.min(1, (now - tile.loaded) / 200);

  			setOpacity(tile.el, fade);
  			if (fade < 1) {
  				nextFrame = true;
  			} else {
  				if (tile.active) {
  					willPrune = true;
  				} else {
  					this._onOpaqueTile(tile);
  				}
  				tile.active = true;
  			}
  		}

  		if (willPrune && !this._noPrune) { this._pruneTiles(); }

  		if (nextFrame) {
  			cancelAnimFrame(this._fadeFrame);
  			this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
  		}
  	},

  	_onOpaqueTile: falseFn,

  	_initContainer: function () {
  		if (this._container) { return; }

  		this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
  		this._updateZIndex();

  		if (this.options.opacity < 1) {
  			this._updateOpacity();
  		}

  		this.getPane().appendChild(this._container);
  	},

  	_updateLevels: function () {

  		var zoom = this._tileZoom,
  		    maxZoom = this.options.maxZoom;

  		if (zoom === undefined) { return undefined; }

  		for (var z in this._levels) {
  			z = Number(z);
  			if (this._levels[z].el.children.length || z === zoom) {
  				this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
  				this._onUpdateLevel(z);
  			} else {
  				remove(this._levels[z].el);
  				this._removeTilesAtZoom(z);
  				this._onRemoveLevel(z);
  				delete this._levels[z];
  			}
  		}

  		var level = this._levels[zoom],
  		    map = this._map;

  		if (!level) {
  			level = this._levels[zoom] = {};

  			level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
  			level.el.style.zIndex = maxZoom;

  			level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
  			level.zoom = zoom;

  			this._setZoomTransform(level, map.getCenter(), map.getZoom());

  			// force the browser to consider the newly added element for transition
  			falseFn(level.el.offsetWidth);

  			this._onCreateLevel(level);
  		}

  		this._level = level;

  		return level;
  	},

  	_onUpdateLevel: falseFn,

  	_onRemoveLevel: falseFn,

  	_onCreateLevel: falseFn,

  	_pruneTiles: function () {
  		if (!this._map) {
  			return;
  		}

  		var key, tile;

  		var zoom = this._map.getZoom();
  		if (zoom > this.options.maxZoom ||
  			zoom < this.options.minZoom) {
  			this._removeAllTiles();
  			return;
  		}

  		for (key in this._tiles) {
  			tile = this._tiles[key];
  			tile.retain = tile.current;
  		}

  		for (key in this._tiles) {
  			tile = this._tiles[key];
  			if (tile.current && !tile.active) {
  				var coords = tile.coords;
  				if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
  					this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
  				}
  			}
  		}

  		for (key in this._tiles) {
  			if (!this._tiles[key].retain) {
  				this._removeTile(key);
  			}
  		}
  	},

  	_removeTilesAtZoom: function (zoom) {
  		for (var key in this._tiles) {
  			if (this._tiles[key].coords.z !== zoom) {
  				continue;
  			}
  			this._removeTile(key);
  		}
  	},

  	_removeAllTiles: function () {
  		for (var key in this._tiles) {
  			this._removeTile(key);
  		}
  	},

  	_invalidateAll: function () {
  		for (var z in this._levels) {
  			remove(this._levels[z].el);
  			this._onRemoveLevel(Number(z));
  			delete this._levels[z];
  		}
  		this._removeAllTiles();

  		this._tileZoom = undefined;
  	},

  	_retainParent: function (x, y, z, minZoom) {
  		var x2 = Math.floor(x / 2),
  		    y2 = Math.floor(y / 2),
  		    z2 = z - 1,
  		    coords2 = new Point(+x2, +y2);
  		coords2.z = +z2;

  		var key = this._tileCoordsToKey(coords2),
  		    tile = this._tiles[key];

  		if (tile && tile.active) {
  			tile.retain = true;
  			return true;

  		} else if (tile && tile.loaded) {
  			tile.retain = true;
  		}

  		if (z2 > minZoom) {
  			return this._retainParent(x2, y2, z2, minZoom);
  		}

  		return false;
  	},

  	_retainChildren: function (x, y, z, maxZoom) {

  		for (var i = 2 * x; i < 2 * x + 2; i++) {
  			for (var j = 2 * y; j < 2 * y + 2; j++) {

  				var coords = new Point(i, j);
  				coords.z = z + 1;

  				var key = this._tileCoordsToKey(coords),
  				    tile = this._tiles[key];

  				if (tile && tile.active) {
  					tile.retain = true;
  					continue;

  				} else if (tile && tile.loaded) {
  					tile.retain = true;
  				}

  				if (z + 1 < maxZoom) {
  					this._retainChildren(i, j, z + 1, maxZoom);
  				}
  			}
  		}
  	},

  	_resetView: function (e) {
  		var animating = e && (e.pinch || e.flyTo);
  		this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
  	},

  	_animateZoom: function (e) {
  		this._setView(e.center, e.zoom, true, e.noUpdate);
  	},

  	_clampZoom: function (zoom) {
  		var options = this.options;

  		if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
  			return options.minNativeZoom;
  		}

  		if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
  			return options.maxNativeZoom;
  		}

  		return zoom;
  	},

  	_setView: function (center, zoom, noPrune, noUpdate) {
  		var tileZoom = Math.round(zoom);
  		if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
  		    (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
  			tileZoom = undefined;
  		} else {
  			tileZoom = this._clampZoom(tileZoom);
  		}

  		var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);

  		if (!noUpdate || tileZoomChanged) {

  			this._tileZoom = tileZoom;

  			if (this._abortLoading) {
  				this._abortLoading();
  			}

  			this._updateLevels();
  			this._resetGrid();

  			if (tileZoom !== undefined) {
  				this._update(center);
  			}

  			if (!noPrune) {
  				this._pruneTiles();
  			}

  			// Flag to prevent _updateOpacity from pruning tiles during
  			// a zoom anim or a pinch gesture
  			this._noPrune = !!noPrune;
  		}

  		this._setZoomTransforms(center, zoom);
  	},

  	_setZoomTransforms: function (center, zoom) {
  		for (var i in this._levels) {
  			this._setZoomTransform(this._levels[i], center, zoom);
  		}
  	},

  	_setZoomTransform: function (level, center, zoom) {
  		var scale = this._map.getZoomScale(zoom, level.zoom),
  		    translate = level.origin.multiplyBy(scale)
  		        .subtract(this._map._getNewPixelOrigin(center, zoom)).round();

  		if (Browser.any3d) {
  			setTransform(level.el, translate, scale);
  		} else {
  			setPosition(level.el, translate);
  		}
  	},

  	_resetGrid: function () {
  		var map = this._map,
  		    crs = map.options.crs,
  		    tileSize = this._tileSize = this.getTileSize(),
  		    tileZoom = this._tileZoom;

  		var bounds = this._map.getPixelWorldBounds(this._tileZoom);
  		if (bounds) {
  			this._globalTileRange = this._pxBoundsToTileRange(bounds);
  		}

  		this._wrapX = crs.wrapLng && !this.options.noWrap && [
  			Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
  			Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
  		];
  		this._wrapY = crs.wrapLat && !this.options.noWrap && [
  			Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
  			Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
  		];
  	},

  	_onMoveEnd: function () {
  		if (!this._map || this._map._animatingZoom) { return; }

  		this._update();
  	},

  	_getTiledPixelBounds: function (center) {
  		var map = this._map,
  		    mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
  		    scale = map.getZoomScale(mapZoom, this._tileZoom),
  		    pixelCenter = map.project(center, this._tileZoom).floor(),
  		    halfSize = map.getSize().divideBy(scale * 2);

  		return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
  	},

  	// Private method to load tiles in the grid's active zoom level according to map bounds
  	_update: function (center) {
  		var map = this._map;
  		if (!map) { return; }
  		var zoom = this._clampZoom(map.getZoom());

  		if (center === undefined) { center = map.getCenter(); }
  		if (this._tileZoom === undefined) { return; }	// if out of minzoom/maxzoom

  		var pixelBounds = this._getTiledPixelBounds(center),
  		    tileRange = this._pxBoundsToTileRange(pixelBounds),
  		    tileCenter = tileRange.getCenter(),
  		    queue = [],
  		    margin = this.options.keepBuffer,
  		    noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
  		                              tileRange.getTopRight().add([margin, -margin]));

  		// Sanity check: panic if the tile range contains Infinity somewhere.
  		if (!(isFinite(tileRange.min.x) &&
  		      isFinite(tileRange.min.y) &&
  		      isFinite(tileRange.max.x) &&
  		      isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }

  		for (var key in this._tiles) {
  			var c = this._tiles[key].coords;
  			if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
  				this._tiles[key].current = false;
  			}
  		}

  		// _update just loads more tiles. If the tile zoom level differs too much
  		// from the map's, let _setView reset levels and prune old tiles.
  		if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }

  		// create a queue of coordinates to load tiles from
  		for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
  			for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
  				var coords = new Point(i, j);
  				coords.z = this._tileZoom;

  				if (!this._isValidTile(coords)) { continue; }

  				var tile = this._tiles[this._tileCoordsToKey(coords)];
  				if (tile) {
  					tile.current = true;
  				} else {
  					queue.push(coords);
  				}
  			}
  		}

  		// sort tile queue to load tiles in order of their distance to center
  		queue.sort(function (a, b) {
  			return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
  		});

  		if (queue.length !== 0) {
  			// if it's the first batch of tiles to load
  			if (!this._loading) {
  				this._loading = true;
  				// @event loading: Event
  				// Fired when the grid layer starts loading tiles.
  				this.fire('loading');
  			}

  			// create DOM fragment to append tiles in one batch
  			var fragment = document.createDocumentFragment();

  			for (i = 0; i < queue.length; i++) {
  				this._addTile(queue[i], fragment);
  			}

  			this._level.el.appendChild(fragment);
  		}
  	},

  	_isValidTile: function (coords) {
  		var crs = this._map.options.crs;

  		if (!crs.infinite) {
  			// don't load tile if it's out of bounds and not wrapped
  			var bounds = this._globalTileRange;
  			if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
  			    (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
  		}

  		if (!this.options.bounds) { return true; }

  		// don't load tile if it doesn't intersect the bounds in options
  		var tileBounds = this._tileCoordsToBounds(coords);
  		return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
  	},

  	_keyToBounds: function (key) {
  		return this._tileCoordsToBounds(this._keyToTileCoords(key));
  	},

  	_tileCoordsToNwSe: function (coords) {
  		var map = this._map,
  		    tileSize = this.getTileSize(),
  		    nwPoint = coords.scaleBy(tileSize),
  		    sePoint = nwPoint.add(tileSize),
  		    nw = map.unproject(nwPoint, coords.z),
  		    se = map.unproject(sePoint, coords.z);
  		return [nw, se];
  	},

  	// converts tile coordinates to its geographical bounds
  	_tileCoordsToBounds: function (coords) {
  		var bp = this._tileCoordsToNwSe(coords),
  		    bounds = new LatLngBounds(bp[0], bp[1]);

  		if (!this.options.noWrap) {
  			bounds = this._map.wrapLatLngBounds(bounds);
  		}
  		return bounds;
  	},
  	// converts tile coordinates to key for the tile cache
  	_tileCoordsToKey: function (coords) {
  		return coords.x + ':' + coords.y + ':' + coords.z;
  	},

  	// converts tile cache key to coordinates
  	_keyToTileCoords: function (key) {
  		var k = key.split(':'),
  		    coords = new Point(+k[0], +k[1]);
  		coords.z = +k[2];
  		return coords;
  	},

  	_removeTile: function (key) {
  		var tile = this._tiles[key];
  		if (!tile) { return; }

  		remove(tile.el);

  		delete this._tiles[key];

  		// @event tileunload: TileEvent
  		// Fired when a tile is removed (e.g. when a tile goes off the screen).
  		this.fire('tileunload', {
  			tile: tile.el,
  			coords: this._keyToTileCoords(key)
  		});
  	},

  	_initTile: function (tile) {
  		addClass(tile, 'leaflet-tile');

  		var tileSize = this.getTileSize();
  		tile.style.width = tileSize.x + 'px';
  		tile.style.height = tileSize.y + 'px';

  		tile.onselectstart = falseFn;
  		tile.onmousemove = falseFn;

  		// update opacity on tiles in IE7-8 because of filter inheritance problems
  		if (Browser.ielt9 && this.options.opacity < 1) {
  			setOpacity(tile, this.options.opacity);
  		}
  	},

  	_addTile: function (coords, container) {
  		var tilePos = this._getTilePos(coords),
  		    key = this._tileCoordsToKey(coords);

  		var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));

  		this._initTile(tile);

  		// if createTile is defined with a second argument ("done" callback),
  		// we know that tile is async and will be ready later; otherwise
  		if (this.createTile.length < 2) {
  			// mark tile as ready, but delay one frame for opacity animation to happen
  			requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
  		}

  		setPosition(tile, tilePos);

  		// save tile in cache
  		this._tiles[key] = {
  			el: tile,
  			coords: coords,
  			current: true
  		};

  		container.appendChild(tile);
  		// @event tileloadstart: TileEvent
  		// Fired when a tile is requested and starts loading.
  		this.fire('tileloadstart', {
  			tile: tile,
  			coords: coords
  		});
  	},

  	_tileReady: function (coords, err, tile) {
  		if (err) {
  			// @event tileerror: TileErrorEvent
  			// Fired when there is an error loading a tile.
  			this.fire('tileerror', {
  				error: err,
  				tile: tile,
  				coords: coords
  			});
  		}

  		var key = this._tileCoordsToKey(coords);

  		tile = this._tiles[key];
  		if (!tile) { return; }

  		tile.loaded = +new Date();
  		if (this._map._fadeAnimated) {
  			setOpacity(tile.el, 0);
  			cancelAnimFrame(this._fadeFrame);
  			this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
  		} else {
  			tile.active = true;
  			this._pruneTiles();
  		}

  		if (!err) {
  			addClass(tile.el, 'leaflet-tile-loaded');

  			// @event tileload: TileEvent
  			// Fired when a tile loads.
  			this.fire('tileload', {
  				tile: tile.el,
  				coords: coords
  			});
  		}

  		if (this._noTilesToLoad()) {
  			this._loading = false;
  			// @event load: Event
  			// Fired when the grid layer loaded all visible tiles.
  			this.fire('load');

  			if (Browser.ielt9 || !this._map._fadeAnimated) {
  				requestAnimFrame(this._pruneTiles, this);
  			} else {
  				// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
  				// to trigger a pruning.
  				setTimeout(bind(this._pruneTiles, this), 250);
  			}
  		}
  	},

  	_getTilePos: function (coords) {
  		return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
  	},

  	_wrapCoords: function (coords) {
  		var newCoords = new Point(
  			this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
  			this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
  		newCoords.z = coords.z;
  		return newCoords;
  	},

  	_pxBoundsToTileRange: function (bounds) {
  		var tileSize = this.getTileSize();
  		return new Bounds(
  			bounds.min.unscaleBy(tileSize).floor(),
  			bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
  	},

  	_noTilesToLoad: function () {
  		for (var key in this._tiles) {
  			if (!this._tiles[key].loaded) { return false; }
  		}
  		return true;
  	}
  });

  // @factory L.gridLayer(options?: GridLayer options)
  // Creates a new instance of GridLayer with the supplied options.
  function gridLayer(options) {
  	return new GridLayer(options);
  }

  /*
   * @class TileLayer
   * @inherits GridLayer
   * @aka L.TileLayer
   * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
   *
   * @example
   *
   * ```js
   * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
   * ```
   *
   * @section URL template
   * @example
   *
   * A string of the following form:
   *
   * ```
   * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
   * ```
   *
   * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "&commat;2x" to the URL to load retina tiles.
   *
   * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
   *
   * ```
   * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
   * ```
   */


  var TileLayer = GridLayer.extend({

  	// @section
  	// @aka TileLayer options
  	options: {
  		// @option minZoom: Number = 0
  		// The minimum zoom level down to which this layer will be displayed (inclusive).
  		minZoom: 0,

  		// @option maxZoom: Number = 18
  		// The maximum zoom level up to which this layer will be displayed (inclusive).
  		maxZoom: 18,

  		// @option subdomains: String|String[] = 'abc'
  		// Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
  		subdomains: 'abc',

  		// @option errorTileUrl: String = ''
  		// URL to the tile image to show in place of the tile that failed to load.
  		errorTileUrl: '',

  		// @option zoomOffset: Number = 0
  		// The zoom number used in tile URLs will be offset with this value.
  		zoomOffset: 0,

  		// @option tms: Boolean = false
  		// If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
  		tms: false,

  		// @option zoomReverse: Boolean = false
  		// If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
  		zoomReverse: false,

  		// @option detectRetina: Boolean = false
  		// If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
  		detectRetina: false,

  		// @option crossOrigin: Boolean|String = false
  		// Whether the crossOrigin attribute will be added to the tiles.
  		// If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
  		crossOrigin: false,

  		// @option referrerPolicy: Boolean|String = false
  		// Whether the referrerPolicy attribute will be added to the tiles.
  		// If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
  		// This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
  		// (e.g. to validate an API token).
  		// Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
  		referrerPolicy: false
  	},

  	initialize: function (url, options) {

  		this._url = url;

  		options = setOptions(this, options);

  		// detecting retina displays, adjusting tileSize and zoom levels
  		if (options.detectRetina && Browser.retina && options.maxZoom > 0) {

  			options.tileSize = Math.floor(options.tileSize / 2);

  			if (!options.zoomReverse) {
  				options.zoomOffset++;
  				options.maxZoom--;
  			} else {
  				options.zoomOffset--;
  				options.minZoom++;
  			}

  			options.minZoom = Math.max(0, options.minZoom);
  		}

  		if (typeof options.subdomains === 'string') {
  			options.subdomains = options.subdomains.split('');
  		}

  		this.on('tileunload', this._onTileRemove);
  	},

  	// @method setUrl(url: String, noRedraw?: Boolean): this
  	// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
  	// If the URL does not change, the layer will not be redrawn unless
  	// the noRedraw parameter is set to false.
  	setUrl: function (url, noRedraw) {
  		if (this._url === url && noRedraw === undefined) {
  			noRedraw = true;
  		}

  		this._url = url;

  		if (!noRedraw) {
  			this.redraw();
  		}
  		return this;
  	},

  	// @method createTile(coords: Object, done?: Function): HTMLElement
  	// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
  	// to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
  	// callback is called when the tile has been loaded.
  	createTile: function (coords, done) {
  		var tile = document.createElement('img');

  		on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
  		on(tile, 'error', bind(this._tileOnError, this, done, tile));

  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
  			tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
  		}

  		// for this new option we follow the documented behavior
  		// more closely by only setting the property when string
  		if (typeof this.options.referrerPolicy === 'string') {
  			tile.referrerPolicy = this.options.referrerPolicy;
  		}

  		/*
  		 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
  		 https://www.w3.org/TR/WCAG20-TECHS/H67
  		*/
  		tile.alt = '';

  		/*
  		 Set role="presentation" to force screen readers to ignore this
  		 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
  		*/
  		tile.setAttribute('role', 'presentation');

  		tile.src = this.getTileUrl(coords);

  		return tile;
  	},

  	// @section Extension methods
  	// @uninheritable
  	// Layers extending `TileLayer` might reimplement the following method.
  	// @method getTileUrl(coords: Object): String
  	// Called only internally, returns the URL for a tile given its coordinates.
  	// Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
  	getTileUrl: function (coords) {
  		var data = {
  			r: Browser.retina ? '@2x' : '',
  			s: this._getSubdomain(coords),
  			x: coords.x,
  			y: coords.y,
  			z: this._getZoomForUrl()
  		};
  		if (this._map && !this._map.options.crs.infinite) {
  			var invertedY = this._globalTileRange.max.y - coords.y;
  			if (this.options.tms) {
  				data['y'] = invertedY;
  			}
  			data['-y'] = invertedY;
  		}

  		return template(this._url, extend(data, this.options));
  	},

  	_tileOnLoad: function (done, tile) {
  		// For https://github.com/Leaflet/Leaflet/issues/3332
  		if (Browser.ielt9) {
  			setTimeout(bind(done, this, null, tile), 0);
  		} else {
  			done(null, tile);
  		}
  	},

  	_tileOnError: function (done, tile, e) {
  		var errorUrl = this.options.errorTileUrl;
  		if (errorUrl && tile.getAttribute('src') !== errorUrl) {
  			tile.src = errorUrl;
  		}
  		done(e, tile);
  	},

  	_onTileRemove: function (e) {
  		e.tile.onload = null;
  	},

  	_getZoomForUrl: function () {
  		var zoom = this._tileZoom,
  		maxZoom = this.options.maxZoom,
  		zoomReverse = this.options.zoomReverse,
  		zoomOffset = this.options.zoomOffset;

  		if (zoomReverse) {
  			zoom = maxZoom - zoom;
  		}

  		return zoom + zoomOffset;
  	},

  	_getSubdomain: function (tilePoint) {
  		var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
  		return this.options.subdomains[index];
  	},

  	// stops loading all tiles in the background layer
  	_abortLoading: function () {
  		var i, tile;
  		for (i in this._tiles) {
  			if (this._tiles[i].coords.z !== this._tileZoom) {
  				tile = this._tiles[i].el;

  				tile.onload = falseFn;
  				tile.onerror = falseFn;

  				if (!tile.complete) {
  					tile.src = emptyImageUrl;
  					var coords = this._tiles[i].coords;
  					remove(tile);
  					delete this._tiles[i];
  					// @event tileabort: TileEvent
  					// Fired when a tile was loading but is now not wanted.
  					this.fire('tileabort', {
  						tile: tile,
  						coords: coords
  					});
  				}
  			}
  		}
  	},

  	_removeTile: function (key) {
  		var tile = this._tiles[key];
  		if (!tile) { return; }

  		// Cancels any pending http requests associated with the tile
  		tile.el.setAttribute('src', emptyImageUrl);

  		return GridLayer.prototype._removeTile.call(this, key);
  	},

  	_tileReady: function (coords, err, tile) {
  		if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
  			return;
  		}

  		return GridLayer.prototype._tileReady.call(this, coords, err, tile);
  	}
  });


  // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
  // Instantiates a tile layer object given a `URL template` and optionally an options object.

  function tileLayer(url, options) {
  	return new TileLayer(url, options);
  }

  /*
   * @class TileLayer.WMS
   * @inherits TileLayer
   * @aka L.TileLayer.WMS
   * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
   *
   * @example
   *
   * ```js
   * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
   * 	layers: 'nexrad-n0r-900913',
   * 	format: 'image/png',
   * 	transparent: true,
   * 	attribution: "Weather data © 2012 IEM Nexrad"
   * });
   * ```
   */

  var TileLayerWMS = TileLayer.extend({

  	// @section
  	// @aka TileLayer.WMS options
  	// If any custom options not documented here are used, they will be sent to the
  	// WMS server as extra parameters in each request URL. This can be useful for
  	// [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
  	defaultWmsParams: {
  		service: 'WMS',
  		request: 'GetMap',

  		// @option layers: String = ''
  		// **(required)** Comma-separated list of WMS layers to show.
  		layers: '',

  		// @option styles: String = ''
  		// Comma-separated list of WMS styles.
  		styles: '',

  		// @option format: String = 'image/jpeg'
  		// WMS image format (use `'image/png'` for layers with transparency).
  		format: 'image/jpeg',

  		// @option transparent: Boolean = false
  		// If `true`, the WMS service will return images with transparency.
  		transparent: false,

  		// @option version: String = '1.1.1'
  		// Version of the WMS service to use
  		version: '1.1.1'
  	},

  	options: {
  		// @option crs: CRS = null
  		// Coordinate Reference System to use for the WMS requests, defaults to
  		// map CRS. Don't change this if you're not sure what it means.
  		crs: null,

  		// @option uppercase: Boolean = false
  		// If `true`, WMS request parameter keys will be uppercase.
  		uppercase: false
  	},

  	initialize: function (url, options) {

  		this._url = url;

  		var wmsParams = extend({}, this.defaultWmsParams);

  		// all keys that are not TileLayer options go to WMS params
  		for (var i in options) {
  			if (!(i in this.options)) {
  				wmsParams[i] = options[i];
  			}
  		}

  		options = setOptions(this, options);

  		var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
  		var tileSize = this.getTileSize();
  		wmsParams.width = tileSize.x * realRetina;
  		wmsParams.height = tileSize.y * realRetina;

  		this.wmsParams = wmsParams;
  	},

  	onAdd: function (map) {

  		this._crs = this.options.crs || map.options.crs;
  		this._wmsVersion = parseFloat(this.wmsParams.version);

  		var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
  		this.wmsParams[projectionKey] = this._crs.code;

  		TileLayer.prototype.onAdd.call(this, map);
  	},

  	getTileUrl: function (coords) {

  		var tileBounds = this._tileCoordsToNwSe(coords),
  		    crs = this._crs,
  		    bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
  		    min = bounds.min,
  		    max = bounds.max,
  		    bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
  		    [min.y, min.x, max.y, max.x] :
  		    [min.x, min.y, max.x, max.y]).join(','),
  		    url = TileLayer.prototype.getTileUrl.call(this, coords);
  		return url +
  			getParamString(this.wmsParams, url, this.options.uppercase) +
  			(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
  	},

  	// @method setParams(params: Object, noRedraw?: Boolean): this
  	// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
  	setParams: function (params, noRedraw) {

  		extend(this.wmsParams, params);

  		if (!noRedraw) {
  			this.redraw();
  		}

  		return this;
  	}
  });


  // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
  // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
  function tileLayerWMS(url, options) {
  	return new TileLayerWMS(url, options);
  }

  TileLayer.WMS = TileLayerWMS;
  tileLayer.wms = tileLayerWMS;

  /*
   * @class Renderer
   * @inherits Layer
   * @aka L.Renderer
   *
   * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
   * DOM container of the renderer, its bounds, and its zoom animation.
   *
   * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
   * itself can be added or removed to the map. All paths use a renderer, which can
   * be implicit (the map will decide the type of renderer and use it automatically)
   * or explicit (using the [`renderer`](#path-renderer) option of the path).
   *
   * Do not use this class directly, use `SVG` and `Canvas` instead.
   *
   * @event update: Event
   * Fired when the renderer updates its bounds, center and zoom, for example when
   * its map has moved
   */

  var Renderer = Layer.extend({

  	// @section
  	// @aka Renderer options
  	options: {
  		// @option padding: Number = 0.1
  		// How much to extend the clip area around the map view (relative to its size)
  		// e.g. 0.1 would be 10% of map view in each direction
  		padding: 0.1
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  		stamp(this);
  		this._layers = this._layers || {};
  	},

  	onAdd: function () {
  		if (!this._container) {
  			this._initContainer(); // defined by renderer implementations

  			if (this._zoomAnimated) {
  				addClass(this._container, 'leaflet-zoom-animated');
  			}
  		}

  		this.getPane().appendChild(this._container);
  		this._update();
  		this.on('update', this._updatePaths, this);
  	},

  	onRemove: function () {
  		this.off('update', this._updatePaths, this);
  		this._destroyContainer();
  	},

  	getEvents: function () {
  		var events = {
  			viewreset: this._reset,
  			zoom: this._onZoom,
  			moveend: this._update,
  			zoomend: this._onZoomEnd
  		};
  		if (this._zoomAnimated) {
  			events.zoomanim = this._onAnimZoom;
  		}
  		return events;
  	},

  	_onAnimZoom: function (ev) {
  		this._updateTransform(ev.center, ev.zoom);
  	},

  	_onZoom: function () {
  		this._updateTransform(this._map.getCenter(), this._map.getZoom());
  	},

  	_updateTransform: function (center, zoom) {
  		var scale = this._map.getZoomScale(zoom, this._zoom),
  		    viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
  		    currentCenterPoint = this._map.project(this._center, zoom),

  		    topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
  				  .subtract(this._map._getNewPixelOrigin(center, zoom));

  		if (Browser.any3d) {
  			setTransform(this._container, topLeftOffset, scale);
  		} else {
  			setPosition(this._container, topLeftOffset);
  		}
  	},

  	_reset: function () {
  		this._update();
  		this._updateTransform(this._center, this._zoom);

  		for (var id in this._layers) {
  			this._layers[id]._reset();
  		}
  	},

  	_onZoomEnd: function () {
  		for (var id in this._layers) {
  			this._layers[id]._project();
  		}
  	},

  	_updatePaths: function () {
  		for (var id in this._layers) {
  			this._layers[id]._update();
  		}
  	},

  	_update: function () {
  		// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
  		// Subclasses are responsible of firing the 'update' event.
  		var p = this.options.padding,
  		    size = this._map.getSize(),
  		    min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();

  		this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());

  		this._center = this._map.getCenter();
  		this._zoom = this._map.getZoom();
  	}
  });

  /*
   * @class Canvas
   * @inherits Renderer
   * @aka L.Canvas
   *
   * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
   * Inherits `Renderer`.
   *
   * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
   * available in all web browsers, notably IE8, and overlapping geometries might
   * not display properly in some edge cases.
   *
   * @example
   *
   * Use Canvas by default for all paths in the map:
   *
   * ```js
   * var map = L.map('map', {
   * 	renderer: L.canvas()
   * });
   * ```
   *
   * Use a Canvas renderer with extra padding for specific vector geometries:
   *
   * ```js
   * var map = L.map('map');
   * var myRenderer = L.canvas({ padding: 0.5 });
   * var line = L.polyline( coordinates, { renderer: myRenderer } );
   * var circle = L.circle( center, { renderer: myRenderer } );
   * ```
   */

  var Canvas = Renderer.extend({

  	// @section
  	// @aka Canvas options
  	options: {
  		// @option tolerance: Number = 0
  		// How much to extend the click tolerance around a path/object on the map.
  		tolerance: 0
  	},

  	getEvents: function () {
  		var events = Renderer.prototype.getEvents.call(this);
  		events.viewprereset = this._onViewPreReset;
  		return events;
  	},

  	_onViewPreReset: function () {
  		// Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
  		this._postponeUpdatePaths = true;
  	},

  	onAdd: function () {
  		Renderer.prototype.onAdd.call(this);

  		// Redraw vectors since canvas is cleared upon removal,
  		// in case of removing the renderer itself from the map.
  		this._draw();
  	},

  	_initContainer: function () {
  		var container = this._container = document.createElement('canvas');

  		on(container, 'mousemove', this._onMouseMove, this);
  		on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
  		on(container, 'mouseout', this._handleMouseOut, this);
  		container['_leaflet_disable_events'] = true;

  		this._ctx = container.getContext('2d');
  	},

  	_destroyContainer: function () {
  		cancelAnimFrame(this._redrawRequest);
  		delete this._ctx;
  		remove(this._container);
  		off(this._container);
  		delete this._container;
  	},

  	_updatePaths: function () {
  		if (this._postponeUpdatePaths) { return; }

  		var layer;
  		this._redrawBounds = null;
  		for (var id in this._layers) {
  			layer = this._layers[id];
  			layer._update();
  		}
  		this._redraw();
  	},

  	_update: function () {
  		if (this._map._animatingZoom && this._bounds) { return; }

  		Renderer.prototype._update.call(this);

  		var b = this._bounds,
  		    container = this._container,
  		    size = b.getSize(),
  		    m = Browser.retina ? 2 : 1;

  		setPosition(container, b.min);

  		// set canvas size (also clearing it); use double size on retina
  		container.width = m * size.x;
  		container.height = m * size.y;
  		container.style.width = size.x + 'px';
  		container.style.height = size.y + 'px';

  		if (Browser.retina) {
  			this._ctx.scale(2, 2);
  		}

  		// translate so we use the same path coordinates after canvas element moves
  		this._ctx.translate(-b.min.x, -b.min.y);

  		// Tell paths to redraw themselves
  		this.fire('update');
  	},

  	_reset: function () {
  		Renderer.prototype._reset.call(this);

  		if (this._postponeUpdatePaths) {
  			this._postponeUpdatePaths = false;
  			this._updatePaths();
  		}
  	},

  	_initPath: function (layer) {
  		this._updateDashArray(layer);
  		this._layers[stamp(layer)] = layer;

  		var order = layer._order = {
  			layer: layer,
  			prev: this._drawLast,
  			next: null
  		};
  		if (this._drawLast) { this._drawLast.next = order; }
  		this._drawLast = order;
  		this._drawFirst = this._drawFirst || this._drawLast;
  	},

  	_addPath: function (layer) {
  		this._requestRedraw(layer);
  	},

  	_removePath: function (layer) {
  		var order = layer._order;
  		var next = order.next;
  		var prev = order.prev;

  		if (next) {
  			next.prev = prev;
  		} else {
  			this._drawLast = prev;
  		}
  		if (prev) {
  			prev.next = next;
  		} else {
  			this._drawFirst = next;
  		}

  		delete layer._order;

  		delete this._layers[stamp(layer)];

  		this._requestRedraw(layer);
  	},

  	_updatePath: function (layer) {
  		// Redraw the union of the layer's old pixel
  		// bounds and the new pixel bounds.
  		this._extendRedrawBounds(layer);
  		layer._project();
  		layer._update();
  		// The redraw will extend the redraw bounds
  		// with the new pixel bounds.
  		this._requestRedraw(layer);
  	},

  	_updateStyle: function (layer) {
  		this._updateDashArray(layer);
  		this._requestRedraw(layer);
  	},

  	_updateDashArray: function (layer) {
  		if (typeof layer.options.dashArray === 'string') {
  			var parts = layer.options.dashArray.split(/[, ]+/),
  			    dashArray = [],
  			    dashValue,
  			    i;
  			for (i = 0; i < parts.length; i++) {
  				dashValue = Number(parts[i]);
  				// Ignore dash array containing invalid lengths
  				if (isNaN(dashValue)) { return; }
  				dashArray.push(dashValue);
  			}
  			layer.options._dashArray = dashArray;
  		} else {
  			layer.options._dashArray = layer.options.dashArray;
  		}
  	},

  	_requestRedraw: function (layer) {
  		if (!this._map) { return; }

  		this._extendRedrawBounds(layer);
  		this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
  	},

  	_extendRedrawBounds: function (layer) {
  		if (layer._pxBounds) {
  			var padding = (layer.options.weight || 0) + 1;
  			this._redrawBounds = this._redrawBounds || new Bounds();
  			this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
  			this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
  		}
  	},

  	_redraw: function () {
  		this._redrawRequest = null;

  		if (this._redrawBounds) {
  			this._redrawBounds.min._floor();
  			this._redrawBounds.max._ceil();
  		}

  		this._clear(); // clear layers in redraw bounds
  		this._draw(); // draw layers

  		this._redrawBounds = null;
  	},

  	_clear: function () {
  		var bounds = this._redrawBounds;
  		if (bounds) {
  			var size = bounds.getSize();
  			this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
  		} else {
  			this._ctx.save();
  			this._ctx.setTransform(1, 0, 0, 1, 0, 0);
  			this._ctx.clearRect(0, 0, this._container.width, this._container.height);
  			this._ctx.restore();
  		}
  	},

  	_draw: function () {
  		var layer, bounds = this._redrawBounds;
  		this._ctx.save();
  		if (bounds) {
  			var size = bounds.getSize();
  			this._ctx.beginPath();
  			this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
  			this._ctx.clip();
  		}

  		this._drawing = true;

  		for (var order = this._drawFirst; order; order = order.next) {
  			layer = order.layer;
  			if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
  				layer._updatePath();
  			}
  		}

  		this._drawing = false;

  		this._ctx.restore();  // Restore state before clipping.
  	},

  	_updatePoly: function (layer, closed) {
  		if (!this._drawing) { return; }

  		var i, j, len2, p,
  		    parts = layer._parts,
  		    len = parts.length,
  		    ctx = this._ctx;

  		if (!len) { return; }

  		ctx.beginPath();

  		for (i = 0; i < len; i++) {
  			for (j = 0, len2 = parts[i].length; j < len2; j++) {
  				p = parts[i][j];
  				ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
  			}
  			if (closed) {
  				ctx.closePath();
  			}
  		}

  		this._fillStroke(ctx, layer);

  		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
  	},

  	_updateCircle: function (layer) {

  		if (!this._drawing || layer._empty()) { return; }

  		var p = layer._point,
  		    ctx = this._ctx,
  		    r = Math.max(Math.round(layer._radius), 1),
  		    s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;

  		if (s !== 1) {
  			ctx.save();
  			ctx.scale(1, s);
  		}

  		ctx.beginPath();
  		ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);

  		if (s !== 1) {
  			ctx.restore();
  		}

  		this._fillStroke(ctx, layer);
  	},

  	_fillStroke: function (ctx, layer) {
  		var options = layer.options;

  		if (options.fill) {
  			ctx.globalAlpha = options.fillOpacity;
  			ctx.fillStyle = options.fillColor || options.color;
  			ctx.fill(options.fillRule || 'evenodd');
  		}

  		if (options.stroke && options.weight !== 0) {
  			if (ctx.setLineDash) {
  				ctx.setLineDash(layer.options && layer.options._dashArray || []);
  			}
  			ctx.globalAlpha = options.opacity;
  			ctx.lineWidth = options.weight;
  			ctx.strokeStyle = options.color;
  			ctx.lineCap = options.lineCap;
  			ctx.lineJoin = options.lineJoin;
  			ctx.stroke();
  		}
  	},

  	// Canvas obviously doesn't have mouse events for individual drawn objects,
  	// so we emulate that by calculating what's under the mouse on mousemove/click manually

  	_onClick: function (e) {
  		var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;

  		for (var order = this._drawFirst; order; order = order.next) {
  			layer = order.layer;
  			if (layer.options.interactive && layer._containsPoint(point)) {
  				if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
  					clickedLayer = layer;
  				}
  			}
  		}
  		this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
  	},

  	_onMouseMove: function (e) {
  		if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }

  		var point = this._map.mouseEventToLayerPoint(e);
  		this._handleMouseHover(e, point);
  	},


  	_handleMouseOut: function (e) {
  		var layer = this._hoveredLayer;
  		if (layer) {
  			// if we're leaving the layer, fire mouseout
  			removeClass(this._container, 'leaflet-interactive');
  			this._fireEvent([layer], e, 'mouseout');
  			this._hoveredLayer = null;
  			this._mouseHoverThrottled = false;
  		}
  	},

  	_handleMouseHover: function (e, point) {
  		if (this._mouseHoverThrottled) {
  			return;
  		}

  		var layer, candidateHoveredLayer;

  		for (var order = this._drawFirst; order; order = order.next) {
  			layer = order.layer;
  			if (layer.options.interactive && layer._containsPoint(point)) {
  				candidateHoveredLayer = layer;
  			}
  		}

  		if (candidateHoveredLayer !== this._hoveredLayer) {
  			this._handleMouseOut(e);

  			if (candidateHoveredLayer) {
  				addClass(this._container, 'leaflet-interactive'); // change cursor
  				this._fireEvent([candidateHoveredLayer], e, 'mouseover');
  				this._hoveredLayer = candidateHoveredLayer;
  			}
  		}

  		this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);

  		this._mouseHoverThrottled = true;
  		setTimeout(bind(function () {
  			this._mouseHoverThrottled = false;
  		}, this), 32);
  	},

  	_fireEvent: function (layers, e, type) {
  		this._map._fireDOMEvent(e, type || e.type, layers);
  	},

  	_bringToFront: function (layer) {
  		var order = layer._order;

  		if (!order) { return; }

  		var next = order.next;
  		var prev = order.prev;

  		if (next) {
  			next.prev = prev;
  		} else {
  			// Already last
  			return;
  		}
  		if (prev) {
  			prev.next = next;
  		} else if (next) {
  			// Update first entry unless this is the
  			// single entry
  			this._drawFirst = next;
  		}

  		order.prev = this._drawLast;
  		this._drawLast.next = order;

  		order.next = null;
  		this._drawLast = order;

  		this._requestRedraw(layer);
  	},

  	_bringToBack: function (layer) {
  		var order = layer._order;

  		if (!order) { return; }

  		var next = order.next;
  		var prev = order.prev;

  		if (prev) {
  			prev.next = next;
  		} else {
  			// Already first
  			return;
  		}
  		if (next) {
  			next.prev = prev;
  		} else if (prev) {
  			// Update last entry unless this is the
  			// single entry
  			this._drawLast = prev;
  		}

  		order.prev = null;

  		order.next = this._drawFirst;
  		this._drawFirst.prev = order;
  		this._drawFirst = order;

  		this._requestRedraw(layer);
  	}
  });

  // @factory L.canvas(options?: Renderer options)
  // Creates a Canvas renderer with the given options.
  function canvas(options) {
  	return Browser.canvas ? new Canvas(options) : null;
  }

  /*
   * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
   */


  var vmlCreate = (function () {
  	try {
  		document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
  		return function (name) {
  			return document.createElement('<lvml:' + name + ' class="lvml">');
  		};
  	} catch (e) {
  		// Do not return fn from catch block so `e` can be garbage collected
  		// See https://github.com/Leaflet/Leaflet/pull/7279
  	}
  	return function (name) {
  		return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
  	};
  })();


  /*
   * @class SVG
   *
   *
   * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
   * with old versions of Internet Explorer.
   */

  // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
  var vmlMixin = {

  	_initContainer: function () {
  		this._container = create$1('div', 'leaflet-vml-container');
  	},

  	_update: function () {
  		if (this._map._animatingZoom) { return; }
  		Renderer.prototype._update.call(this);
  		this.fire('update');
  	},

  	_initPath: function (layer) {
  		var container = layer._container = vmlCreate('shape');

  		addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));

  		container.coordsize = '1 1';

  		layer._path = vmlCreate('path');
  		container.appendChild(layer._path);

  		this._updateStyle(layer);
  		this._layers[stamp(layer)] = layer;
  	},

  	_addPath: function (layer) {
  		var container = layer._container;
  		this._container.appendChild(container);

  		if (layer.options.interactive) {
  			layer.addInteractiveTarget(container);
  		}
  	},

  	_removePath: function (layer) {
  		var container = layer._container;
  		remove(container);
  		layer.removeInteractiveTarget(container);
  		delete this._layers[stamp(layer)];
  	},

  	_updateStyle: function (layer) {
  		var stroke = layer._stroke,
  		    fill = layer._fill,
  		    options = layer.options,
  		    container = layer._container;

  		container.stroked = !!options.stroke;
  		container.filled = !!options.fill;

  		if (options.stroke) {
  			if (!stroke) {
  				stroke = layer._stroke = vmlCreate('stroke');
  			}
  			container.appendChild(stroke);
  			stroke.weight = options.weight + 'px';
  			stroke.color = options.color;
  			stroke.opacity = options.opacity;

  			if (options.dashArray) {
  				stroke.dashStyle = isArray(options.dashArray) ?
  				    options.dashArray.join(' ') :
  				    options.dashArray.replace(/( *, *)/g, ' ');
  			} else {
  				stroke.dashStyle = '';
  			}
  			stroke.endcap = options.lineCap.replace('butt', 'flat');
  			stroke.joinstyle = options.lineJoin;

  		} else if (stroke) {
  			container.removeChild(stroke);
  			layer._stroke = null;
  		}

  		if (options.fill) {
  			if (!fill) {
  				fill = layer._fill = vmlCreate('fill');
  			}
  			container.appendChild(fill);
  			fill.color = options.fillColor || options.color;
  			fill.opacity = options.fillOpacity;

  		} else if (fill) {
  			container.removeChild(fill);
  			layer._fill = null;
  		}
  	},

  	_updateCircle: function (layer) {
  		var p = layer._point.round(),
  		    r = Math.round(layer._radius),
  		    r2 = Math.round(layer._radiusY || r);

  		this._setPath(layer, layer._empty() ? 'M0 0' :
  			'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
  	},

  	_setPath: function (layer, path) {
  		layer._path.v = path;
  	},

  	_bringToFront: function (layer) {
  		toFront(layer._container);
  	},

  	_bringToBack: function (layer) {
  		toBack(layer._container);
  	}
  };

  var create = Browser.vml ? vmlCreate : svgCreate;

  /*
   * @class SVG
   * @inherits Renderer
   * @aka L.SVG
   *
   * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
   * Inherits `Renderer`.
   *
   * Due to [technical limitations](https://caniuse.com/svg), SVG is not
   * available in all web browsers, notably Android 2.x and 3.x.
   *
   * Although SVG is not available on IE7 and IE8, these browsers support
   * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
   * (a now deprecated technology), and the SVG renderer will fall back to VML in
   * this case.
   *
   * @example
   *
   * Use SVG by default for all paths in the map:
   *
   * ```js
   * var map = L.map('map', {
   * 	renderer: L.svg()
   * });
   * ```
   *
   * Use a SVG renderer with extra padding for specific vector geometries:
   *
   * ```js
   * var map = L.map('map');
   * var myRenderer = L.svg({ padding: 0.5 });
   * var line = L.polyline( coordinates, { renderer: myRenderer } );
   * var circle = L.circle( center, { renderer: myRenderer } );
   * ```
   */

  var SVG = Renderer.extend({

  	_initContainer: function () {
  		this._container = create('svg');

  		// makes it possible to click through svg root; we'll reset it back in individual paths
  		this._container.setAttribute('pointer-events', 'none');

  		this._rootGroup = create('g');
  		this._container.appendChild(this._rootGroup);
  	},

  	_destroyContainer: function () {
  		remove(this._container);
  		off(this._container);
  		delete this._container;
  		delete this._rootGroup;
  		delete this._svgSize;
  	},

  	_update: function () {
  		if (this._map._animatingZoom && this._bounds) { return; }

  		Renderer.prototype._update.call(this);

  		var b = this._bounds,
  		    size = b.getSize(),
  		    container = this._container;

  		// set size of svg-container if changed
  		if (!this._svgSize || !this._svgSize.equals(size)) {
  			this._svgSize = size;
  			container.setAttribute('width', size.x);
  			container.setAttribute('height', size.y);
  		}

  		// movement: update container viewBox so that we don't have to change coordinates of individual layers
  		setPosition(container, b.min);
  		container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));

  		this.fire('update');
  	},

  	// methods below are called by vector layers implementations

  	_initPath: function (layer) {
  		var path = layer._path = create('path');

  		// @namespace Path
  		// @option className: String = null
  		// Custom class name set on an element. Only for SVG renderer.
  		if (layer.options.className) {
  			addClass(path, layer.options.className);
  		}

  		if (layer.options.interactive) {
  			addClass(path, 'leaflet-interactive');
  		}

  		this._updateStyle(layer);
  		this._layers[stamp(layer)] = layer;
  	},

  	_addPath: function (layer) {
  		if (!this._rootGroup) { this._initContainer(); }
  		this._rootGroup.appendChild(layer._path);
  		layer.addInteractiveTarget(layer._path);
  	},

  	_removePath: function (layer) {
  		remove(layer._path);
  		layer.removeInteractiveTarget(layer._path);
  		delete this._layers[stamp(layer)];
  	},

  	_updatePath: function (layer) {
  		layer._project();
  		layer._update();
  	},

  	_updateStyle: function (layer) {
  		var path = layer._path,
  		    options = layer.options;

  		if (!path) { return; }

  		if (options.stroke) {
  			path.setAttribute('stroke', options.color);
  			path.setAttribute('stroke-opacity', options.opacity);
  			path.setAttribute('stroke-width', options.weight);
  			path.setAttribute('stroke-linecap', options.lineCap);
  			path.setAttribute('stroke-linejoin', options.lineJoin);

  			if (options.dashArray) {
  				path.setAttribute('stroke-dasharray', options.dashArray);
  			} else {
  				path.removeAttribute('stroke-dasharray');
  			}

  			if (options.dashOffset) {
  				path.setAttribute('stroke-dashoffset', options.dashOffset);
  			} else {
  				path.removeAttribute('stroke-dashoffset');
  			}
  		} else {
  			path.setAttribute('stroke', 'none');
  		}

  		if (options.fill) {
  			path.setAttribute('fill', options.fillColor || options.color);
  			path.setAttribute('fill-opacity', options.fillOpacity);
  			path.setAttribute('fill-rule', options.fillRule || 'evenodd');
  		} else {
  			path.setAttribute('fill', 'none');
  		}
  	},

  	_updatePoly: function (layer, closed) {
  		this._setPath(layer, pointsToPath(layer._parts, closed));
  	},

  	_updateCircle: function (layer) {
  		var p = layer._point,
  		    r = Math.max(Math.round(layer._radius), 1),
  		    r2 = Math.max(Math.round(layer._radiusY), 1) || r,
  		    arc = 'a' + r + ',' + r2 + ' 0 1,0 ';

  		// drawing a circle with two half-arcs
  		var d = layer._empty() ? 'M0 0' :
  			'M' + (p.x - r) + ',' + p.y +
  			arc + (r * 2) + ',0 ' +
  			arc + (-r * 2) + ',0 ';

  		this._setPath(layer, d);
  	},

  	_setPath: function (layer, path) {
  		layer._path.setAttribute('d', path);
  	},

  	// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
  	_bringToFront: function (layer) {
  		toFront(layer._path);
  	},

  	_bringToBack: function (layer) {
  		toBack(layer._path);
  	}
  });

  if (Browser.vml) {
  	SVG.include(vmlMixin);
  }

  // @namespace SVG
  // @factory L.svg(options?: Renderer options)
  // Creates a SVG renderer with the given options.
  function svg(options) {
  	return Browser.svg || Browser.vml ? new SVG(options) : null;
  }

  Map.include({
  	// @namespace Map; @method getRenderer(layer: Path): Renderer
  	// Returns the instance of `Renderer` that should be used to render the given
  	// `Path`. It will ensure that the `renderer` options of the map and paths
  	// are respected, and that the renderers do exist on the map.
  	getRenderer: function (layer) {
  		// @namespace Path; @option renderer: Renderer
  		// Use this specific instance of `Renderer` for this path. Takes
  		// precedence over the map's [default renderer](#map-renderer).
  		var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;

  		if (!renderer) {
  			renderer = this._renderer = this._createRenderer();
  		}

  		if (!this.hasLayer(renderer)) {
  			this.addLayer(renderer);
  		}
  		return renderer;
  	},

  	_getPaneRenderer: function (name) {
  		if (name === 'overlayPane' || name === undefined) {
  			return false;
  		}

  		var renderer = this._paneRenderers[name];
  		if (renderer === undefined) {
  			renderer = this._createRenderer({pane: name});
  			this._paneRenderers[name] = renderer;
  		}
  		return renderer;
  	},

  	_createRenderer: function (options) {
  		// @namespace Map; @option preferCanvas: Boolean = false
  		// Whether `Path`s should be rendered on a `Canvas` renderer.
  		// By default, all `Path`s are rendered in a `SVG` renderer.
  		return (this.options.preferCanvas && canvas(options)) || svg(options);
  	}
  });

  /*
   * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
   */

  /*
   * @class Rectangle
   * @aka L.Rectangle
   * @inherits Polygon
   *
   * A class for drawing rectangle overlays on a map. Extends `Polygon`.
   *
   * @example
   *
   * ```js
   * // define rectangle geographical bounds
   * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
   *
   * // create an orange rectangle
   * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
   *
   * // zoom the map to the rectangle bounds
   * map.fitBounds(bounds);
   * ```
   *
   */


  var Rectangle = Polygon.extend({
  	initialize: function (latLngBounds, options) {
  		Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
  	},

  	// @method setBounds(latLngBounds: LatLngBounds): this
  	// Redraws the rectangle with the passed bounds.
  	setBounds: function (latLngBounds) {
  		return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
  	},

  	_boundsToLatLngs: function (latLngBounds) {
  		latLngBounds = toLatLngBounds(latLngBounds);
  		return [
  			latLngBounds.getSouthWest(),
  			latLngBounds.getNorthWest(),
  			latLngBounds.getNorthEast(),
  			latLngBounds.getSouthEast()
  		];
  	}
  });


  // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
  function rectangle(latLngBounds, options) {
  	return new Rectangle(latLngBounds, options);
  }

  SVG.create = create;
  SVG.pointsToPath = pointsToPath;

  GeoJSON.geometryToLayer = geometryToLayer;
  GeoJSON.coordsToLatLng = coordsToLatLng;
  GeoJSON.coordsToLatLngs = coordsToLatLngs;
  GeoJSON.latLngToCoords = latLngToCoords;
  GeoJSON.latLngsToCoords = latLngsToCoords;
  GeoJSON.getFeature = getFeature;
  GeoJSON.asFeature = asFeature;

  /*
   * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
   * (zoom to a selected bounding box), enabled by default.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @option boxZoom: Boolean = true
  	// Whether the map can be zoomed to a rectangular area specified by
  	// dragging the mouse while pressing the shift key.
  	boxZoom: true
  });

  var BoxZoom = Handler.extend({
  	initialize: function (map) {
  		this._map = map;
  		this._container = map._container;
  		this._pane = map._panes.overlayPane;
  		this._resetStateTimeout = 0;
  		map.on('unload', this._destroy, this);
  	},

  	addHooks: function () {
  		on(this._container, 'mousedown', this._onMouseDown, this);
  	},

  	removeHooks: function () {
  		off(this._container, 'mousedown', this._onMouseDown, this);
  	},

  	moved: function () {
  		return this._moved;
  	},

  	_destroy: function () {
  		remove(this._pane);
  		delete this._pane;
  	},

  	_resetState: function () {
  		this._resetStateTimeout = 0;
  		this._moved = false;
  	},

  	_clearDeferredResetState: function () {
  		if (this._resetStateTimeout !== 0) {
  			clearTimeout(this._resetStateTimeout);
  			this._resetStateTimeout = 0;
  		}
  	},

  	_onMouseDown: function (e) {
  		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }

  		// Clear the deferred resetState if it hasn't executed yet, otherwise it
  		// will interrupt the interaction and orphan a box element in the container.
  		this._clearDeferredResetState();
  		this._resetState();

  		disableTextSelection();
  		disableImageDrag();

  		this._startPoint = this._map.mouseEventToContainerPoint(e);

  		on(document, {
  			contextmenu: stop,
  			mousemove: this._onMouseMove,
  			mouseup: this._onMouseUp,
  			keydown: this._onKeyDown
  		}, this);
  	},

  	_onMouseMove: function (e) {
  		if (!this._moved) {
  			this._moved = true;

  			this._box = create$1('div', 'leaflet-zoom-box', this._container);
  			addClass(this._container, 'leaflet-crosshair');

  			this._map.fire('boxzoomstart');
  		}

  		this._point = this._map.mouseEventToContainerPoint(e);

  		var bounds = new Bounds(this._point, this._startPoint),
  		    size = bounds.getSize();

  		setPosition(this._box, bounds.min);

  		this._box.style.width  = size.x + 'px';
  		this._box.style.height = size.y + 'px';
  	},

  	_finish: function () {
  		if (this._moved) {
  			remove(this._box);
  			removeClass(this._container, 'leaflet-crosshair');
  		}

  		enableTextSelection();
  		enableImageDrag();

  		off(document, {
  			contextmenu: stop,
  			mousemove: this._onMouseMove,
  			mouseup: this._onMouseUp,
  			keydown: this._onKeyDown
  		}, this);
  	},

  	_onMouseUp: function (e) {
  		if ((e.which !== 1) && (e.button !== 1)) { return; }

  		this._finish();

  		if (!this._moved) { return; }
  		// Postpone to next JS tick so internal click event handling
  		// still see it as "moved".
  		this._clearDeferredResetState();
  		this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);

  		var bounds = new LatLngBounds(
  		        this._map.containerPointToLatLng(this._startPoint),
  		        this._map.containerPointToLatLng(this._point));

  		this._map
  			.fitBounds(bounds)
  			.fire('boxzoomend', {boxZoomBounds: bounds});
  	},

  	_onKeyDown: function (e) {
  		if (e.keyCode === 27) {
  			this._finish();
  			this._clearDeferredResetState();
  			this._resetState();
  		}
  	}
  });

  // @section Handlers
  // @property boxZoom: Handler
  // Box (shift-drag with mouse) zoom handler.
  Map.addInitHook('addHandler', 'boxZoom', BoxZoom);

  /*
   * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
   */

  // @namespace Map
  // @section Interaction Options

  Map.mergeOptions({
  	// @option doubleClickZoom: Boolean|String = true
  	// Whether the map can be zoomed in by double clicking on it and
  	// zoomed out by double clicking while holding shift. If passed
  	// `'center'`, double-click zoom will zoom to the center of the
  	//  view regardless of where the mouse was.
  	doubleClickZoom: true
  });

  var DoubleClickZoom = Handler.extend({
  	addHooks: function () {
  		this._map.on('dblclick', this._onDoubleClick, this);
  	},

  	removeHooks: function () {
  		this._map.off('dblclick', this._onDoubleClick, this);
  	},

  	_onDoubleClick: function (e) {
  		var map = this._map,
  		    oldZoom = map.getZoom(),
  		    delta = map.options.zoomDelta,
  		    zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;

  		if (map.options.doubleClickZoom === 'center') {
  			map.setZoom(zoom);
  		} else {
  			map.setZoomAround(e.containerPoint, zoom);
  		}
  	}
  });

  // @section Handlers
  //
  // Map properties include interaction handlers that allow you to control
  // interaction behavior in runtime, enabling or disabling certain features such
  // as dragging or touch zoom (see `Handler` methods). For example:
  //
  // ```js
  // map.doubleClickZoom.disable();
  // ```
  //
  // @property doubleClickZoom: Handler
  // Double click zoom handler.
  Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);

  /*
   * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @option dragging: Boolean = true
  	// Whether the map is draggable with mouse/touch or not.
  	dragging: true,

  	// @section Panning Inertia Options
  	// @option inertia: Boolean = *
  	// If enabled, panning of the map will have an inertia effect where
  	// the map builds momentum while dragging and continues moving in
  	// the same direction for some time. Feels especially nice on touch
  	// devices. Enabled by default.
  	inertia: true,

  	// @option inertiaDeceleration: Number = 3000
  	// The rate with which the inertial movement slows down, in pixels/second².
  	inertiaDeceleration: 3400, // px/s^2

  	// @option inertiaMaxSpeed: Number = Infinity
  	// Max speed of the inertial movement, in pixels/second.
  	inertiaMaxSpeed: Infinity, // px/s

  	// @option easeLinearity: Number = 0.2
  	easeLinearity: 0.2,

  	// TODO refactor, move to CRS
  	// @option worldCopyJump: Boolean = false
  	// With this option enabled, the map tracks when you pan to another "copy"
  	// of the world and seamlessly jumps to the original one so that all overlays
  	// like markers and vector layers are still visible.
  	worldCopyJump: false,

  	// @option maxBoundsViscosity: Number = 0.0
  	// If `maxBounds` is set, this option will control how solid the bounds
  	// are when dragging the map around. The default value of `0.0` allows the
  	// user to drag outside the bounds at normal speed, higher values will
  	// slow down map dragging outside bounds, and `1.0` makes the bounds fully
  	// solid, preventing the user from dragging outside the bounds.
  	maxBoundsViscosity: 0.0
  });

  var Drag = Handler.extend({
  	addHooks: function () {
  		if (!this._draggable) {
  			var map = this._map;

  			this._draggable = new Draggable(map._mapPane, map._container);

  			this._draggable.on({
  				dragstart: this._onDragStart,
  				drag: this._onDrag,
  				dragend: this._onDragEnd
  			}, this);

  			this._draggable.on('predrag', this._onPreDragLimit, this);
  			if (map.options.worldCopyJump) {
  				this._draggable.on('predrag', this._onPreDragWrap, this);
  				map.on('zoomend', this._onZoomEnd, this);

  				map.whenReady(this._onZoomEnd, this);
  			}
  		}
  		addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
  		this._draggable.enable();
  		this._positions = [];
  		this._times = [];
  	},

  	removeHooks: function () {
  		removeClass(this._map._container, 'leaflet-grab');
  		removeClass(this._map._container, 'leaflet-touch-drag');
  		this._draggable.disable();
  	},

  	moved: function () {
  		return this._draggable && this._draggable._moved;
  	},

  	moving: function () {
  		return this._draggable && this._draggable._moving;
  	},

  	_onDragStart: function () {
  		var map = this._map;

  		map._stop();
  		if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
  			var bounds = toLatLngBounds(this._map.options.maxBounds);

  			this._offsetLimit = toBounds(
  				this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
  				this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
  					.add(this._map.getSize()));

  			this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
  		} else {
  			this._offsetLimit = null;
  		}

  		map
  		    .fire('movestart')
  		    .fire('dragstart');

  		if (map.options.inertia) {
  			this._positions = [];
  			this._times = [];
  		}
  	},

  	_onDrag: function (e) {
  		if (this._map.options.inertia) {
  			var time = this._lastTime = +new Date(),
  			    pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;

  			this._positions.push(pos);
  			this._times.push(time);

  			this._prunePositions(time);
  		}

  		this._map
  		    .fire('move', e)
  		    .fire('drag', e);
  	},

  	_prunePositions: function (time) {
  		while (this._positions.length > 1 && time - this._times[0] > 50) {
  			this._positions.shift();
  			this._times.shift();
  		}
  	},

  	_onZoomEnd: function () {
  		var pxCenter = this._map.getSize().divideBy(2),
  		    pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);

  		this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
  		this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
  	},

  	_viscousLimit: function (value, threshold) {
  		return value - (value - threshold) * this._viscosity;
  	},

  	_onPreDragLimit: function () {
  		if (!this._viscosity || !this._offsetLimit) { return; }

  		var offset = this._draggable._newPos.subtract(this._draggable._startPos);

  		var limit = this._offsetLimit;
  		if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
  		if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
  		if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
  		if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }

  		this._draggable._newPos = this._draggable._startPos.add(offset);
  	},

  	_onPreDragWrap: function () {
  		// TODO refactor to be able to adjust map pane position after zoom
  		var worldWidth = this._worldWidth,
  		    halfWidth = Math.round(worldWidth / 2),
  		    dx = this._initialWorldOffset,
  		    x = this._draggable._newPos.x,
  		    newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
  		    newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
  		    newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;

  		this._draggable._absPos = this._draggable._newPos.clone();
  		this._draggable._newPos.x = newX;
  	},

  	_onDragEnd: function (e) {
  		var map = this._map,
  		    options = map.options,

  		    noInertia = !options.inertia || e.noInertia || this._times.length < 2;

  		map.fire('dragend', e);

  		if (noInertia) {
  			map.fire('moveend');

  		} else {
  			this._prunePositions(+new Date());

  			var direction = this._lastPos.subtract(this._positions[0]),
  			    duration = (this._lastTime - this._times[0]) / 1000,
  			    ease = options.easeLinearity,

  			    speedVector = direction.multiplyBy(ease / duration),
  			    speed = speedVector.distanceTo([0, 0]),

  			    limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
  			    limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),

  			    decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
  			    offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();

  			if (!offset.x && !offset.y) {
  				map.fire('moveend');

  			} else {
  				offset = map._limitOffset(offset, map.options.maxBounds);

  				requestAnimFrame(function () {
  					map.panBy(offset, {
  						duration: decelerationDuration,
  						easeLinearity: ease,
  						noMoveStart: true,
  						animate: true
  					});
  				});
  			}
  		}
  	}
  });

  // @section Handlers
  // @property dragging: Handler
  // Map dragging handler (by both mouse and touch).
  Map.addInitHook('addHandler', 'dragging', Drag);

  /*
   * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
   */

  // @namespace Map
  // @section Keyboard Navigation Options
  Map.mergeOptions({
  	// @option keyboard: Boolean = true
  	// Makes the map focusable and allows users to navigate the map with keyboard
  	// arrows and `+`/`-` keys.
  	keyboard: true,

  	// @option keyboardPanDelta: Number = 80
  	// Amount of pixels to pan when pressing an arrow key.
  	keyboardPanDelta: 80
  });

  var Keyboard = Handler.extend({

  	keyCodes: {
  		left:    [37],
  		right:   [39],
  		down:    [40],
  		up:      [38],
  		zoomIn:  [187, 107, 61, 171],
  		zoomOut: [189, 109, 54, 173]
  	},

  	initialize: function (map) {
  		this._map = map;

  		this._setPanDelta(map.options.keyboardPanDelta);
  		this._setZoomDelta(map.options.zoomDelta);
  	},

  	addHooks: function () {
  		var container = this._map._container;

  		// make the container focusable by tabbing
  		if (container.tabIndex <= 0) {
  			container.tabIndex = '0';
  		}

  		on(container, {
  			focus: this._onFocus,
  			blur: this._onBlur,
  			mousedown: this._onMouseDown
  		}, this);

  		this._map.on({
  			focus: this._addHooks,
  			blur: this._removeHooks
  		}, this);
  	},

  	removeHooks: function () {
  		this._removeHooks();

  		off(this._map._container, {
  			focus: this._onFocus,
  			blur: this._onBlur,
  			mousedown: this._onMouseDown
  		}, this);

  		this._map.off({
  			focus: this._addHooks,
  			blur: this._removeHooks
  		}, this);
  	},

  	_onMouseDown: function () {
  		if (this._focused) { return; }

  		var body = document.body,
  		    docEl = document.documentElement,
  		    top = body.scrollTop || docEl.scrollTop,
  		    left = body.scrollLeft || docEl.scrollLeft;

  		this._map._container.focus();

  		window.scrollTo(left, top);
  	},

  	_onFocus: function () {
  		this._focused = true;
  		this._map.fire('focus');
  	},

  	_onBlur: function () {
  		this._focused = false;
  		this._map.fire('blur');
  	},

  	_setPanDelta: function (panDelta) {
  		var keys = this._panKeys = {},
  		    codes = this.keyCodes,
  		    i, len;

  		for (i = 0, len = codes.left.length; i < len; i++) {
  			keys[codes.left[i]] = [-1 * panDelta, 0];
  		}
  		for (i = 0, len = codes.right.length; i < len; i++) {
  			keys[codes.right[i]] = [panDelta, 0];
  		}
  		for (i = 0, len = codes.down.length; i < len; i++) {
  			keys[codes.down[i]] = [0, panDelta];
  		}
  		for (i = 0, len = codes.up.length; i < len; i++) {
  			keys[codes.up[i]] = [0, -1 * panDelta];
  		}
  	},

  	_setZoomDelta: function (zoomDelta) {
  		var keys = this._zoomKeys = {},
  		    codes = this.keyCodes,
  		    i, len;

  		for (i = 0, len = codes.zoomIn.length; i < len; i++) {
  			keys[codes.zoomIn[i]] = zoomDelta;
  		}
  		for (i = 0, len = codes.zoomOut.length; i < len; i++) {
  			keys[codes.zoomOut[i]] = -zoomDelta;
  		}
  	},

  	_addHooks: function () {
  		on(document, 'keydown', this._onKeyDown, this);
  	},

  	_removeHooks: function () {
  		off(document, 'keydown', this._onKeyDown, this);
  	},

  	_onKeyDown: function (e) {
  		if (e.altKey || e.ctrlKey || e.metaKey) { return; }

  		var key = e.keyCode,
  		    map = this._map,
  		    offset;

  		if (key in this._panKeys) {
  			if (!map._panAnim || !map._panAnim._inProgress) {
  				offset = this._panKeys[key];
  				if (e.shiftKey) {
  					offset = toPoint(offset).multiplyBy(3);
  				}

  				map.panBy(offset);

  				if (map.options.maxBounds) {
  					map.panInsideBounds(map.options.maxBounds);
  				}
  			}
  		} else if (key in this._zoomKeys) {
  			map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);

  		} else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
  			map.closePopup();

  		} else {
  			return;
  		}

  		stop(e);
  	}
  });

  // @section Handlers
  // @section Handlers
  // @property keyboard: Handler
  // Keyboard navigation handler.
  Map.addInitHook('addHandler', 'keyboard', Keyboard);

  /*
   * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @section Mouse wheel options
  	// @option scrollWheelZoom: Boolean|String = true
  	// Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
  	// it will zoom to the center of the view regardless of where the mouse was.
  	scrollWheelZoom: true,

  	// @option wheelDebounceTime: Number = 40
  	// Limits the rate at which a wheel can fire (in milliseconds). By default
  	// user can't zoom via wheel more often than once per 40 ms.
  	wheelDebounceTime: 40,

  	// @option wheelPxPerZoomLevel: Number = 60
  	// How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
  	// mean a change of one full zoom level. Smaller values will make wheel-zooming
  	// faster (and vice versa).
  	wheelPxPerZoomLevel: 60
  });

  var ScrollWheelZoom = Handler.extend({
  	addHooks: function () {
  		on(this._map._container, 'wheel', this._onWheelScroll, this);

  		this._delta = 0;
  	},

  	removeHooks: function () {
  		off(this._map._container, 'wheel', this._onWheelScroll, this);
  	},

  	_onWheelScroll: function (e) {
  		var delta = getWheelDelta(e);

  		var debounce = this._map.options.wheelDebounceTime;

  		this._delta += delta;
  		this._lastMousePos = this._map.mouseEventToContainerPoint(e);

  		if (!this._startTime) {
  			this._startTime = +new Date();
  		}

  		var left = Math.max(debounce - (+new Date() - this._startTime), 0);

  		clearTimeout(this._timer);
  		this._timer = setTimeout(bind(this._performZoom, this), left);

  		stop(e);
  	},

  	_performZoom: function () {
  		var map = this._map,
  		    zoom = map.getZoom(),
  		    snap = this._map.options.zoomSnap || 0;

  		map._stop(); // stop panning and fly animations if any

  		// map the delta with a sigmoid function to -4..4 range leaning on -1..1
  		var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
  		    d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
  		    d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
  		    delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;

  		this._delta = 0;
  		this._startTime = null;

  		if (!delta) { return; }

  		if (map.options.scrollWheelZoom === 'center') {
  			map.setZoom(zoom + delta);
  		} else {
  			map.setZoomAround(this._lastMousePos, zoom + delta);
  		}
  	}
  });

  // @section Handlers
  // @property scrollWheelZoom: Handler
  // Scroll wheel zoom handler.
  Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);

  /*
   * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
   * which otherwise is not fired by mobile Safari.
   */

  var tapHoldDelay = 600;

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @section Touch interaction options
  	// @option tapHold: Boolean
  	// Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
  	tapHold: Browser.touchNative && Browser.safari && Browser.mobile,

  	// @option tapTolerance: Number = 15
  	// The max number of pixels a user can shift his finger during touch
  	// for it to be considered a valid tap.
  	tapTolerance: 15
  });

  var TapHold = Handler.extend({
  	addHooks: function () {
  		on(this._map._container, 'touchstart', this._onDown, this);
  	},

  	removeHooks: function () {
  		off(this._map._container, 'touchstart', this._onDown, this);
  	},

  	_onDown: function (e) {
  		clearTimeout(this._holdTimeout);
  		if (e.touches.length !== 1) { return; }

  		var first = e.touches[0];
  		this._startPos = this._newPos = new Point(first.clientX, first.clientY);

  		this._holdTimeout = setTimeout(bind(function () {
  			this._cancel();
  			if (!this._isTapValid()) { return; }

  			// prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
  			on(document, 'touchend', preventDefault);
  			on(document, 'touchend touchcancel', this._cancelClickPrevent);
  			this._simulateEvent('contextmenu', first);
  		}, this), tapHoldDelay);

  		on(document, 'touchend touchcancel contextmenu', this._cancel, this);
  		on(document, 'touchmove', this._onMove, this);
  	},

  	_cancelClickPrevent: function cancelClickPrevent() {
  		off(document, 'touchend', preventDefault);
  		off(document, 'touchend touchcancel', cancelClickPrevent);
  	},

  	_cancel: function () {
  		clearTimeout(this._holdTimeout);
  		off(document, 'touchend touchcancel contextmenu', this._cancel, this);
  		off(document, 'touchmove', this._onMove, this);
  	},

  	_onMove: function (e) {
  		var first = e.touches[0];
  		this._newPos = new Point(first.clientX, first.clientY);
  	},

  	_isTapValid: function () {
  		return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
  	},

  	_simulateEvent: function (type, e) {
  		var simulatedEvent = new MouseEvent(type, {
  			bubbles: true,
  			cancelable: true,
  			view: window,
  			// detail: 1,
  			screenX: e.screenX,
  			screenY: e.screenY,
  			clientX: e.clientX,
  			clientY: e.clientY,
  			// button: 2,
  			// buttons: 2
  		});

  		simulatedEvent._simulated = true;

  		e.target.dispatchEvent(simulatedEvent);
  	}
  });

  // @section Handlers
  // @property tapHold: Handler
  // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
  Map.addInitHook('addHandler', 'tapHold', TapHold);

  /*
   * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @section Touch interaction options
  	// @option touchZoom: Boolean|String = *
  	// Whether the map can be zoomed by touch-dragging with two fingers. If
  	// passed `'center'`, it will zoom to the center of the view regardless of
  	// where the touch events (fingers) were. Enabled for touch-capable web
  	// browsers.
  	touchZoom: Browser.touch,

  	// @option bounceAtZoomLimits: Boolean = true
  	// Set it to false if you don't want the map to zoom beyond min/max zoom
  	// and then bounce back when pinch-zooming.
  	bounceAtZoomLimits: true
  });

  var TouchZoom = Handler.extend({
  	addHooks: function () {
  		addClass(this._map._container, 'leaflet-touch-zoom');
  		on(this._map._container, 'touchstart', this._onTouchStart, this);
  	},

  	removeHooks: function () {
  		removeClass(this._map._container, 'leaflet-touch-zoom');
  		off(this._map._container, 'touchstart', this._onTouchStart, this);
  	},

  	_onTouchStart: function (e) {
  		var map = this._map;
  		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }

  		var p1 = map.mouseEventToContainerPoint(e.touches[0]),
  		    p2 = map.mouseEventToContainerPoint(e.touches[1]);

  		this._centerPoint = map.getSize()._divideBy(2);
  		this._startLatLng = map.containerPointToLatLng(this._centerPoint);
  		if (map.options.touchZoom !== 'center') {
  			this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
  		}

  		this._startDist = p1.distanceTo(p2);
  		this._startZoom = map.getZoom();

  		this._moved = false;
  		this._zooming = true;

  		map._stop();

  		on(document, 'touchmove', this._onTouchMove, this);
  		on(document, 'touchend touchcancel', this._onTouchEnd, this);

  		preventDefault(e);
  	},

  	_onTouchMove: function (e) {
  		if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }

  		var map = this._map,
  		    p1 = map.mouseEventToContainerPoint(e.touches[0]),
  		    p2 = map.mouseEventToContainerPoint(e.touches[1]),
  		    scale = p1.distanceTo(p2) / this._startDist;

  		this._zoom = map.getScaleZoom(scale, this._startZoom);

  		if (!map.options.bounceAtZoomLimits && (
  			(this._zoom < map.getMinZoom() && scale < 1) ||
  			(this._zoom > map.getMaxZoom() && scale > 1))) {
  			this._zoom = map._limitZoom(this._zoom);
  		}

  		if (map.options.touchZoom === 'center') {
  			this._center = this._startLatLng;
  			if (scale === 1) { return; }
  		} else {
  			// Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
  			var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
  			if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
  			this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
  		}

  		if (!this._moved) {
  			map._moveStart(true, false);
  			this._moved = true;
  		}

  		cancelAnimFrame(this._animRequest);

  		var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
  		this._animRequest = requestAnimFrame(moveFn, this, true);

  		preventDefault(e);
  	},

  	_onTouchEnd: function () {
  		if (!this._moved || !this._zooming) {
  			this._zooming = false;
  			return;
  		}

  		this._zooming = false;
  		cancelAnimFrame(this._animRequest);

  		off(document, 'touchmove', this._onTouchMove, this);
  		off(document, 'touchend touchcancel', this._onTouchEnd, this);

  		// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
  		if (this._map.options.zoomAnimation) {
  			this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
  		} else {
  			this._map._resetView(this._center, this._map._limitZoom(this._zoom));
  		}
  	}
  });

  // @section Handlers
  // @property touchZoom: Handler
  // Touch zoom handler.
  Map.addInitHook('addHandler', 'touchZoom', TouchZoom);

  Map.BoxZoom = BoxZoom;
  Map.DoubleClickZoom = DoubleClickZoom;
  Map.Drag = Drag;
  Map.Keyboard = Keyboard;
  Map.ScrollWheelZoom = ScrollWheelZoom;
  Map.TapHold = TapHold;
  Map.TouchZoom = TouchZoom;

  exports.Bounds = Bounds;
  exports.Browser = Browser;
  exports.CRS = CRS;
  exports.Canvas = Canvas;
  exports.Circle = Circle;
  exports.CircleMarker = CircleMarker;
  exports.Class = Class;
  exports.Control = Control;
  exports.DivIcon = DivIcon;
  exports.DivOverlay = DivOverlay;
  exports.DomEvent = DomEvent;
  exports.DomUtil = DomUtil;
  exports.Draggable = Draggable;
  exports.Evented = Evented;
  exports.FeatureGroup = FeatureGroup;
  exports.GeoJSON = GeoJSON;
  exports.GridLayer = GridLayer;
  exports.Handler = Handler;
  exports.Icon = Icon;
  exports.ImageOverlay = ImageOverlay;
  exports.LatLng = LatLng;
  exports.LatLngBounds = LatLngBounds;
  exports.Layer = Layer;
  exports.LayerGroup = LayerGroup;
  exports.LineUtil = LineUtil;
  exports.Map = Map;
  exports.Marker = Marker;
  exports.Mixin = Mixin;
  exports.Path = Path;
  exports.Point = Point;
  exports.PolyUtil = PolyUtil;
  exports.Polygon = Polygon;
  exports.Polyline = Polyline;
  exports.Popup = Popup;
  exports.PosAnimation = PosAnimation;
  exports.Projection = index;
  exports.Rectangle = Rectangle;
  exports.Renderer = Renderer;
  exports.SVG = SVG;
  exports.SVGOverlay = SVGOverlay;
  exports.TileLayer = TileLayer;
  exports.Tooltip = Tooltip;
  exports.Transformation = Transformation;
  exports.Util = Util;
  exports.VideoOverlay = VideoOverlay;
  exports.bind = bind;
  exports.bounds = toBounds;
  exports.canvas = canvas;
  exports.circle = circle;
  exports.circleMarker = circleMarker;
  exports.control = control;
  exports.divIcon = divIcon;
  exports.extend = extend;
  exports.featureGroup = featureGroup;
  exports.geoJSON = geoJSON;
  exports.geoJson = geoJson;
  exports.gridLayer = gridLayer;
  exports.icon = icon;
  exports.imageOverlay = imageOverlay;
  exports.latLng = toLatLng;
  exports.latLngBounds = toLatLngBounds;
  exports.layerGroup = layerGroup;
  exports.map = createMap;
  exports.marker = marker;
  exports.point = toPoint;
  exports.polygon = polygon;
  exports.polyline = polyline;
  exports.popup = popup;
  exports.rectangle = rectangle;
  exports.setOptions = setOptions;
  exports.stamp = stamp;
  exports.svg = svg;
  exports.svgOverlay = svgOverlay;
  exports.tileLayer = tileLayer;
  exports.tooltip = tooltip;
  exports.transformation = toTransformation;
  exports.version = version;
  exports.videoOverlay = videoOverlay;

  var oldL = window.L;
  exports.noConflict = function() {
  	window.L = oldL;
  	return this;
  }
  // Always export us to window global (see #2364)
  window.L = exports;

}));
//# sourceMappingURL=leaflet-src.js.map

/*!
 * Justified Gallery - v3.5.4
 * http://miromannino.github.io/Justified-Gallery/
 * Copyright (c) 2015 Miro Mannino
 * Licensed under the MIT license.
 */
(function($) {

  /* Events
    jg.complete : called when all the gallery has been created
    jg.resize : called when the gallery has been resized
  */

  $.fn.justifiedGallery = function (arg) {

    // Default options
    var defaults = {
      sizeRangeSuffixes : {
        'lt100': '',  // e.g. Flickr uses '_t'
        'lt240': '',  // e.g. Flickr uses '_m' 
        'lt320': '',  // e.g. Flickr uses '_n' 
        'lt500': '',  // e.g. Flickr uses '' 
        'lt640': '',  // e.g. Flickr uses '_z'
        'lt1024': '', // e.g. Flickr uses '_b'
      },
      rowHeight : 120,
      maxRowHeight : 0, // negative value = no limits, 0 = 1.5 * rowHeight
      margins : 1,
      border: -1, // negative value = same as margins, 0 = disabled

      lastRow : 'nojustify', // or can be 'justify' or 'hide'
      justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified 
                                  (i.e. lastRow setting is not considered) */
      fixedHeight : false,
      waitThumbnailsLoad : true,
      captions : true,
      cssAnimation: false,
      imagesAnimationDuration : 500, // ignored with css animations
      captionSettings : { // ignored with css animations
        animationDuration : 500,
        visibleOpacity : 0.7, 
        nonVisibleOpacity : 0.0 
      },
      rel : null, // rewrite the rel of each analyzed links
      target : null, // rewrite the target of all links
      extension : /\.[^.\\/]+$/,
      refreshTime : 100,
      randomize : false
    };

    function getSuffix(width, height, context) {
      var longestSide;
      longestSide = (width > height) ? width : height;
      if (longestSide <= 100) {
        return context.settings.sizeRangeSuffixes.lt100;
      } else if (longestSide <= 240) {
        return context.settings.sizeRangeSuffixes.lt240;
      } else if (longestSide <= 320) {
        return context.settings.sizeRangeSuffixes.lt320;
      } else if (longestSide <= 500) {
        return context.settings.sizeRangeSuffixes.lt500;
      } else if (longestSide <= 640) {
        return context.settings.sizeRangeSuffixes.lt640;
      } else {
        return context.settings.sizeRangeSuffixes.lt1024;
      }
    }

    function endsWith(str, suffix) {
      return str.indexOf(suffix, str.length - suffix.length) !== -1;
    }

    function removeSuffix(str, suffix) {
      return str.substring(0, str.length - suffix.length);
    }

    function getUsedSuffix(str, context) {
      var voidSuffix = false;
      for (var si in context.settings.sizeRangeSuffixes) {
        if (context.settings.sizeRangeSuffixes[si].length === 0) {
          voidSuffix = true;
          continue;
        }
        if (endsWith(str, context.settings.sizeRangeSuffixes[si])) {
          return context.settings.sizeRangeSuffixes[si];
        }
      }

      if (voidSuffix) return "";
      else throw 'unknown suffix for ' + str;
    }

    /* Given an image src, with the width and the height, returns the new image src with the
       best suffix to show the best quality thumbnail. */
    function newSrc(imageSrc, imgWidth, imgHeight, context) {
      var matchRes = imageSrc.match(context.settings.extension);
      var ext = (matchRes != null) ? matchRes[0] : '';
      var newImageSrc = imageSrc.replace(context.settings.extension, '');
      newImageSrc = removeSuffix(newImageSrc, getUsedSuffix(newImageSrc, context));
      newImageSrc += getSuffix(imgWidth, imgHeight, context) + ext;
      return newImageSrc;
    }

    function onEntryMouseEnterForCaption (ev) {
      var $caption = $(ev.currentTarget).find('.caption');
      if (ev.data.settings.cssAnimation) {
        $caption.addClass('caption-visible').removeClass('caption-hidden');
      } else {
        $caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration, 
                               ev.data.settings.captionSettings.visibleOpacity);
      }
    }

    function onEntryMouseLeaveForCaption (ev) {
      var $caption = $(ev.currentTarget).find('.caption');
      if (ev.data.settings.cssAnimation) {
        $caption.removeClass('caption-visible').removeClass('caption-hidden');
      } else {
        $caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration, 
                               ev.data.settings.captionSettings.nonVisibleOpacity);
      }
    }

    function showImg($entry, callback, context) {
      if (context.settings.cssAnimation) {
        $entry.addClass('entry-visible');
        callback();
      } else {
        $entry.stop().fadeTo(context.settings.imagesAnimationDuration, 1.0, callback);
      }
    }

    function hideImgImmediately($entry, context) {
      if (context.settings.cssAnimation) {
        $entry.removeClass('entry-visible');
      } else {
        $entry.stop().fadeTo(0, 0);
      }
    }

    function imgFromEntry($entry) {
      var $img = $entry.find('> img');
      if ($img.length === 0) $img = $entry.find('> a > img');    
      return $img;
    }

    function displayEntry($entry, x, y, imgWidth, imgHeight, rowHeight, context) {
      var $image = imgFromEntry($entry);
      $image.css('width', imgWidth);
      $image.css('height', imgHeight);
      //if ($entry.get(0) === $image.parent().get(0)) { // this creates an error in link_around_img test
        $image.css('margin-left', - imgWidth / 2);
        $image.css('margin-top', - imgHeight / 2);
      //}
      $entry.width(imgWidth);
      $entry.height(rowHeight);
      $entry.css('top', y);
      $entry.css('left', x);

      //DEBUG// console.log('displayEntry (w: ' + $image.width() + ' h: ' + $image.height());

      // Image reloading for an high quality of thumbnails
      var imageSrc = $image.attr('src');
      var newImageSrc = newSrc(imageSrc, imgWidth, imgHeight, context);

      $image.one('error', function () {
        //DEBUG// console.log('revert the original image');
        $image.attr('src', $image.data('jg.originalSrc')); //revert to the original thumbnail, we got it.
      });

      function loadNewImage() {
        if (imageSrc !== newImageSrc) { //load the new image after the fadeIn
          $image.attr('src', newImageSrc);
        }
      }

      if ($image.data('jg.loaded') === 'skipped') {
        onImageEvent(imageSrc, function() {
          showImg($entry, loadNewImage, context);
          $image.data('jg.loaded', true);
        });
      } else {
        showImg($entry, loadNewImage, context);
      }

      // Captions ------------------------------
      var captionMouseEvents = $entry.data('jg.captionMouseEvents');
      if (context.settings.captions === true) {
        var $imgCaption = $entry.find('.caption');
        if ($imgCaption.length === 0) { // Create it if it doesn't exists
          var caption = $image.attr('alt');
          if (typeof caption === 'undefined') caption = $entry.attr('title');
          if (typeof caption !== 'undefined') { // Create only we found something
            $imgCaption = $('<div class="caption">' + caption + '</div>');
            $entry.append($imgCaption);
          }
        }
      
        // Create events (we check again the $imgCaption because it can be still inexistent)
        if ($imgCaption.length !== 0) {
          if (!context.settings.cssAnimation) {
            $imgCaption.stop().fadeTo(context.settings.imagesAnimationDuration, 
                                      context.settings.captionSettings.nonVisibleOpacity); 
          }
          if (typeof captionMouseEvents === 'undefined') {
            captionMouseEvents = {
              mouseenter: onEntryMouseEnterForCaption,
              mouseleave: onEntryMouseLeaveForCaption
            };
            $entry.on('mouseenter', undefined, context, captionMouseEvents.mouseenter);
            $entry.on('mouseleave', undefined, context, captionMouseEvents.mouseleave);
            $entry.data('jg.captionMouseEvents', captionMouseEvents);
          }
        }
      } else {
        if (typeof captionMouseEvents !== 'undefined') {
          $entry.off('mouseenter', undefined, context, captionMouseEvents.mouseenter);
          $entry.off('mouseleave', undefined, context, captionMouseEvents.mouseleave);
          $entry.removeData('jg.captionMouseEvents');
        }
      }

    }

    function prepareBuildingRow(context, isLastRow) {
      var settings = context.settings;
      var i, $entry, $image, imgAspectRatio, newImgW, newImgH, justify = true;
      var minHeight = 0;
      var availableWidth = context.galleryWidth - 2 * context.border - (
                            (context.buildingRow.entriesBuff.length - 1) * settings.margins);
      var rowHeight = availableWidth / context.buildingRow.aspectRatio;
      var justificable = context.buildingRow.width / availableWidth > settings.justifyThreshold;

      //Skip the last row if we can't justify it and the lastRow == 'hide'
      if (isLastRow && settings.lastRow === 'hide' && !justificable) {
        for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
          $entry = context.buildingRow.entriesBuff[i];
          if (settings.cssAnimation) 
            $entry.removeClass('entry-visible');            
          else
            $entry.stop().fadeTo(0, 0);
        }
        return -1;
      }

      // With lastRow = nojustify, justify if is justificable (the images will not become too big)
      if (isLastRow && !justificable && settings.lastRow === 'nojustify') justify = false;

      for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
        $image = imgFromEntry(context.buildingRow.entriesBuff[i]);
        imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');

        if (justify) {
          newImgW = (i === context.buildingRow.entriesBuff.length - 1) ? availableWidth 
                      : rowHeight * imgAspectRatio;
          newImgH = rowHeight;

          /* With fixedHeight the newImgH must be greater than rowHeight. 
          In some cases here this is not satisfied (due to the justification).
          But we comment it, because is better to have a shorter but justified row instead 
          to have a cropped image at the end. */
          /*if (settings.fixedHeight && newImgH < settings.rowHeight) {
            newImgW = settings.rowHeight * imgAspectRatio;
            newImgH = settings.rowHeight;
          }*/

        } else {
          newImgW = settings.rowHeight * imgAspectRatio;
          newImgH = settings.rowHeight;
        }

        availableWidth -= Math.round(newImgW);
        $image.data('jg.jimgw', Math.round(newImgW));
        $image.data('jg.jimgh', Math.ceil(newImgH));
        if (i === 0 || minHeight > newImgH) minHeight = newImgH;
      }

      if (settings.fixedHeight && minHeight > settings.rowHeight) 
        minHeight = settings.rowHeight;

      return {minHeight: minHeight, justify: justify};
    }

    function rewind(context) {
      context.lastAnalyzedIndex = -1;
      context.buildingRow.entriesBuff = [];
      context.buildingRow.aspectRatio = 0;
      context.buildingRow.width = 0;
      context.offY = context.border;
    }

    function flushRow(context, isLastRow) {
      var settings = context.settings;
      var $entry, $image, minHeight, buildingRowRes, offX = context.border;

      //DEBUG// console.log('flush (isLastRow: ' + isLastRow + ')');

      buildingRowRes = prepareBuildingRow(context, isLastRow);
      minHeight = buildingRowRes.minHeight;
      if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) {
        context.buildingRow.entriesBuff = [];
        context.buildingRow.aspectRatio = 0;
        context.buildingRow.width = 0;
        return;
      }

      if (settings.maxRowHeight > 0 && settings.maxRowHeight < minHeight)
        minHeight = settings.maxRowHeight;
      else if (settings.maxRowHeight === 0 && (1.5 * settings.rowHeight) < minHeight)
        minHeight = 1.5 * settings.rowHeight;

      for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
        $entry = context.buildingRow.entriesBuff[i];
        $image = imgFromEntry($entry);
        displayEntry($entry, offX, context.offY, $image.data('jg.jimgw'), 
                     $image.data('jg.jimgh'), minHeight, context);
        offX += $image.data('jg.jimgw') + settings.margins;
      }

      //Gallery Height
      context.$gallery.height(context.offY + minHeight + context.border + 
        (context.spinner.active ? context.spinner.$el.innerHeight() : 0)
      );

      if (!isLastRow || (minHeight <= context.settings.rowHeight && buildingRowRes.justify)) {
        //Ready for a new row
        context.offY += minHeight + context.settings.margins;

        //DEBUG// console.log('minHeight: ' + minHeight + ' offY: ' + context.offY);

        context.buildingRow.entriesBuff = []; //clear the array creating a new one
        context.buildingRow.aspectRatio = 0;
        context.buildingRow.width = 0;
        context.$gallery.trigger('jg.rowflush');
      }
    }

    function checkWidth(context) {
      context.checkWidthIntervalId = setInterval(function () {
        var galleryWidth = parseInt(context.$gallery.width(), 10);
        if (context.galleryWidth !== galleryWidth) {
          //DEBUG// console.log("resize. old: " + context.galleryWidth + " new: " + galleryWidth);
          
          context.galleryWidth = galleryWidth;
          rewind(context);

          // Restart to analyze
          startImgAnalyzer(context, true);
        }
      }, context.settings.refreshTime);
    } 

    function startLoadingSpinnerAnimation(spinnerContext) {
      clearInterval(spinnerContext.intervalId);
      spinnerContext.intervalId = setInterval(function () {
        if (spinnerContext.phase < spinnerContext.$points.length) 
          spinnerContext.$points.eq(spinnerContext.phase).fadeTo(spinnerContext.timeslot, 1);
        else
          spinnerContext.$points.eq(spinnerContext.phase - spinnerContext.$points.length)
                        .fadeTo(spinnerContext.timeslot, 0);
        spinnerContext.phase = (spinnerContext.phase + 1) % (spinnerContext.$points.length * 2);
      }, spinnerContext.timeslot);
    }

    function stopLoadingSpinnerAnimation(spinnerContext) {
      clearInterval(spinnerContext.intervalId);
      spinnerContext.intervalId = null;
    }

    function stopImgAnalyzerStarter(context) {
      context.yield.flushed = 0;
      if (context.imgAnalyzerTimeout !== null) clearTimeout(context.imgAnalyzerTimeout);
    }

    function startImgAnalyzer(context, isForResize) {
      stopImgAnalyzerStarter(context);
      context.imgAnalyzerTimeout = setTimeout(function () { 
        analyzeImages(context, isForResize); 
      }, 0.001);
      analyzeImages(context, isForResize);
    }

    function analyzeImages(context, isForResize) {
      
      /* //DEBUG// 
      var rnd = parseInt(Math.random() * 10000, 10);
      console.log('analyzeImages ' + rnd + ' start');
      console.log('images status: ');
      for (var i = 0; i < context.entries.length; i++) {
        var $entry = $(context.entries[i]);
        var $image = imgFromEntry($entry);
        console.log(i + ' (alt: ' + $image.attr('alt') + 'loaded: ' + $image.data('jg.loaded') + ')');
      }*/

      /* The first row */
      var settings = context.settings;
      var isLastRow;
      
      for (var i = context.lastAnalyzedIndex + 1; i < context.entries.length; i++) {
        var $entry = $(context.entries[i]);
        var $image = imgFromEntry($entry);

        if ($image.data('jg.loaded') === true || $image.data('jg.loaded') === 'skipped') {
          isLastRow = i >= context.entries.length - 1;

          var availableWidth = context.galleryWidth - 2 * context.border - (
                               (context.buildingRow.entriesBuff.length - 1) * settings.margins);
          var imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');
          if (availableWidth / (context.buildingRow.aspectRatio + imgAspectRatio) < settings.rowHeight) {
            flushRow(context, isLastRow);
            if(++context.yield.flushed >= context.yield.every) {
              //DEBUG// console.log("yield");
              startImgAnalyzer(context, isForResize);
              return;
            }
          }

          context.buildingRow.entriesBuff.push($entry);
          context.buildingRow.aspectRatio += imgAspectRatio;
          context.buildingRow.width += imgAspectRatio * settings.rowHeight;
          context.lastAnalyzedIndex = i;

        } else if ($image.data('jg.loaded') !== 'error') {
          return;
        }
      }

      // Last row flush (the row is not full)
      if (context.buildingRow.entriesBuff.length > 0) flushRow(context, true);

      if (context.spinner.active) {
        context.spinner.active = false;
        context.$gallery.height(context.$gallery.height() - context.spinner.$el.innerHeight());
        context.spinner.$el.detach();
        stopLoadingSpinnerAnimation(context.spinner);
      }

      /* Stop, if there is, the timeout to start the analyzeImages.
          This is because an image can be set loaded, and the timeout can be set,
          but this image can be analyzed yet. 
      */
      stopImgAnalyzerStarter(context);

      //On complete callback
      if (!isForResize) 
        context.$gallery.trigger('jg.complete'); 
      else 
        context.$gallery.trigger('jg.resize');

      //DEBUG// console.log('analyzeImages ' + rnd +  ' end');
    }

    function checkSettings (context) {
      var settings = context.settings;

      function checkSuffixesRange(range) {
        if (typeof settings.sizeRangeSuffixes[range] !== 'string')
          throw 'sizeRangeSuffixes.' + range + ' must be a string';
      }

      function checkOrConvertNumber(parent, settingName) {
        if (typeof parent[settingName] === 'string') {
          parent[settingName] = parseFloat(parent[settingName], 10);
          if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
        } else if (typeof parent[settingName] === 'number') {
          if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
        } else {
          throw settingName + ' must be a number';
        }
      }

      if (typeof settings.sizeRangeSuffixes !== 'object')
        throw 'sizeRangeSuffixes must be defined and must be an object';

      checkSuffixesRange('lt100');
      checkSuffixesRange('lt240');
      checkSuffixesRange('lt320');
      checkSuffixesRange('lt500');
      checkSuffixesRange('lt640');
      checkSuffixesRange('lt1024');

      checkOrConvertNumber(settings, 'rowHeight');
      checkOrConvertNumber(settings, 'maxRowHeight');

      if (settings.maxRowHeight > 0 && 
          settings.maxRowHeight < settings.rowHeight) {
        settings.maxRowHeight = settings.rowHeight;
      }
      
      checkOrConvertNumber(settings, 'margins');
      checkOrConvertNumber(settings, 'border');

      if (settings.lastRow !== 'nojustify' &&
          settings.lastRow !== 'justify' &&
          settings.lastRow !== 'hide') {
        throw 'lastRow must be "nojustify", "justify" or "hide"';
      }

      checkOrConvertNumber(settings, 'justifyThreshold');
      if (settings.justifyThreshold < 0 || settings.justifyThreshold > 1)
        throw 'justifyThreshold must be in the interval [0,1]';
      if (typeof settings.cssAnimation !== 'boolean') {
        throw 'cssAnimation must be a boolean'; 
      }
      
      checkOrConvertNumber(settings.captionSettings, 'animationDuration');
      checkOrConvertNumber(settings, 'imagesAnimationDuration');

      checkOrConvertNumber(settings.captionSettings, 'visibleOpacity');
      if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
        throw 'captionSettings.visibleOpacity must be in the interval [0, 1]';

      checkOrConvertNumber(settings.captionSettings, 'nonVisibleOpacity');
      if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
        throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]';

      if (typeof settings.fixedHeight !== 'boolean') {
        throw 'fixedHeight must be a boolean';  
      }

      if (typeof settings.captions !== 'boolean') {
        throw 'captions must be a boolean'; 
      }

      checkOrConvertNumber(settings, 'refreshTime');

      if (typeof settings.randomize !== 'boolean') {
        throw 'randomize must be a boolean';  
      }

    }

    function onImageEvent(imageSrc, onLoad, onError) {
      if (!onLoad && !onError) {
        return;
      }
      /* Check if the image is loaded or not using another image object.
       We cannot use the 'complete' image property, because some browsers,
       with a 404 set complete = true */
      var memImage = new Image();
      var $memImage = $(memImage);
      if (onLoad) {
        $memImage.one('load', function () {
          $memImage.off('load error');
          onLoad(memImage);
        });
      }
      if (onError) {
        $memImage.one('error', function() {
          $memImage.off('load error');
          onError(memImage);
        });
      }
      memImage.src = imageSrc;
    }

    return this.each(function (index, gallery) {

      var $gallery = $(gallery);
      $gallery.addClass('justified-gallery');

      var context = $gallery.data('jg.context');
      if (typeof context === 'undefined') {

        if (typeof arg !== 'undefined' && arg !== null && typeof arg !== 'object') 
          throw 'The argument must be an object';

        // Spinner init
        var $spinner = $('<div class="spinner"><span></span><span></span><span></span></div>');
        var extendedSettings = $.extend({}, defaults, arg);

        var border = extendedSettings.border >= 0 ? extendedSettings.border : extendedSettings.margins;

        //Context init
        context = {
          settings : extendedSettings,
          imgAnalyzerTimeout : null,
          entries : null,
          buildingRow : {
            entriesBuff : [],
            width : 0,
            aspectRatio : 0
          },
          lastAnalyzedIndex : -1,
          yield : {
            every : 2, /* do a flush every context.yield.every flushes (
                  * must be greater than 1, else the analyzeImages will loop */
            flushed : 0 //flushed rows without a yield
          },
          border : border,
          offY : border,
          spinner : {
            active : false,
            phase : 0,
            timeslot : 150,
            $el : $spinner,
            $points : $spinner.find('span'),
            intervalId : null
          },
          checkWidthIntervalId : null,
          galleryWidth :  $gallery.width(),
          $gallery : $gallery
        };

        $gallery.data('jg.context', context);

      } else if (arg === 'norewind') {
        /* Hide the image of the buildingRow to prevent strange effects when the row will be
           re-justified again */
        for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
          hideImgImmediately(context.buildingRow.entriesBuff[i], context);
        }
        // In this case we don't rewind, and analyze all the images
      } else {
        context.settings = $.extend({}, context.settings, arg);
        context.border = context.settings.border >= 0 ? context.settings.border : context.settings.margins;
        rewind(context);
      }
      
      checkSettings(context);

      context.entries = $gallery.find('> a, > div:not(.spinner)').toArray();
      if (context.entries.length === 0) return;

      // Randomize
      if (context.settings.randomize) {
        context.entries.sort(function () { return Math.random() * 2 - 1; });
        $.each(context.entries, function () {
          $(this).appendTo($gallery);
        });
      }

      var imagesToLoad = false;
      var skippedImages = false;
      $.each(context.entries, function (index, entry) {
        var $entry = $(entry);
        var $image = imgFromEntry($entry);

        $entry.addClass('jg-entry');

        if ($image.data('jg.loaded') !== true && $image.data('jg.loaded') !== 'skipped') {

          // Link Rel global overwrite
          if (context.settings.rel !== null) $entry.attr('rel', context.settings.rel);

          // Link Target global overwrite
          if (context.settings.target !== null) $entry.attr('target', context.settings.target);

          // Image src
          var imageSrc = (typeof $image.data('safe-src') !== 'undefined') ? 
                            $image.data('safe-src') : $image.attr('src');
          $image.data('jg.originalSrc', imageSrc);
          $image.attr('src', imageSrc);

          var width = parseInt($image.attr('width'), 10);
          var height = parseInt($image.attr('height'), 10);
          if(context.settings.waitThumbnailsLoad !== true && !isNaN(width) && !isNaN(height)) {
            $image.data('jg.imgw', width);
            $image.data('jg.imgh', height);
            $image.data('jg.loaded', 'skipped');
            skippedImages = true;
            startImgAnalyzer(context, false);
            return true;
          }

          $image.data('jg.loaded', false);
          imagesToLoad = true;

          // Spinner start
          if (context.spinner.active === false) {
            context.spinner.active = true;
            $gallery.append(context.spinner.$el);
            $gallery.height(context.offY + context.spinner.$el.innerHeight());
            startLoadingSpinnerAnimation(context.spinner);
          }

          onImageEvent(imageSrc, function imgLoaded (loadImg) {
            //DEBUG// console.log('img load (alt: ' + $image.attr('alt') + ')');
            $image.data('jg.imgw', loadImg.width);
            $image.data('jg.imgh', loadImg.height);
            $image.data('jg.loaded', true);
            startImgAnalyzer(context, false);
          }, function imgLoadError () {
            //DEBUG// console.log('img error (alt: ' + $image.attr('alt') + ')');
            $image.data('jg.loaded', 'error');
            startImgAnalyzer(context, false);
          });

        }

      });

      if (!imagesToLoad && !skippedImages) startImgAnalyzer(context, false);
      checkWidth(context);
    });

  };
  
}(jQuery));
/**
 * IMPORTANT WOODMART NOTICE: this file is a modified version of the original isotope.pkgd.js file.
 * If you update this file, ensure to re-apply fixes (see: https://github.com/metafizzy/flickity/pull/900).
 */

/*!
 * Isotope PACKAGED v3.0.6
 *
 * Licensed GPLv3 for open source use
 * or Isotope Commercial License for commercial use
 *
 * https://isotope.metafizzy.co
 * Copyright 2010-2018 Metafizzy
 */

/**
 * Bridget makes jQuery widgets
 * v2.0.1
 * MIT license
 */

/* jshint browser: true, strict: true, undef: true, unused: true */

( function( window, factory ) {
  // universal module definition
  /*jshint strict: false */ /* globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) {
      return factory( window, jQuery );
    });
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('jquery')
    );
  } else {
    // browser global
    window.jQueryBridget = factory(
        window,
        window.jQuery
    );
  }

}( window, function factory( window, jQuery ) {
  'use strict';

// ----- utils ----- //

  var arraySlice = Array.prototype.slice;

// helper function for logging errors
// $.error breaks jQuery chaining
  var console = window.console;
  var logError = typeof console == 'undefined' ? function() {} :
      function( message ) {
        console.error( message );
      };

// ----- jQueryBridget ----- //

  function jQueryBridget( namespace, PluginClass, $ ) {
    $ = $ || jQuery || window.jQuery;
    if ( !$ ) {
      return;
    }

    // add option method -> $().plugin('option', {...})
    if ( !PluginClass.prototype.option ) {
      // option setter
      PluginClass.prototype.option = function( opts ) {
        // bail out if not an object
        if ( !$.isPlainObject( opts ) ){
          return;
        }
        this.options = $.extend( true, this.options, opts );
      };
    }

    // make jQuery plugin
    $.fn[ namespace ] = function( arg0 /*, arg1 */ ) {
      if ( typeof arg0 == 'string' ) {
        // method call $().plugin( 'methodName', { options } )
        // shift arguments by 1
        var args = arraySlice.call( arguments, 1 );
        return methodCall( this, arg0, args );
      }
      // just $().plugin({ options })
      plainCall( this, arg0 );
      return this;
    };

    // $().plugin('methodName')
    function methodCall( $elems, methodName, args ) {
      var returnValue;
      var pluginMethodStr = '$().' + namespace + '("' + methodName + '")';

      $elems.each( function( i, elem ) {
        // get instance
        var instance = $.data( elem, namespace );
        if ( !instance ) {
          logError( namespace + ' not initialized. Cannot call methods, i.e. ' +
              pluginMethodStr );
          return;
        }

        var method = instance[ methodName ];
        if ( !method || methodName.charAt(0) == '_' ) {
          logError( pluginMethodStr + ' is not a valid method' );
          return;
        }

        // apply method, get return value
        var value = method.apply( instance, args );
        // set return value if value is returned, use only first value
        returnValue = returnValue === undefined ? value : returnValue;
      });

      return returnValue !== undefined ? returnValue : $elems;
    }

    function plainCall( $elems, options ) {
      $elems.each( function( i, elem ) {
        var instance = $.data( elem, namespace );
        if ( instance ) {
          // set options & init
          instance.option( options );
          instance._init();
        } else {
          // initialize new instance
          instance = new PluginClass( elem, options );
          $.data( elem, namespace, instance );
        }
      });
    }

    updateJQuery( $ );

  }

// ----- updateJQuery ----- //

// set $.bridget for v1 backwards compatibility
  function updateJQuery( $ ) {
    if ( !$ || ( $ && $.bridget ) ) {
      return;
    }
    $.bridget = jQueryBridget;
  }

  updateJQuery( jQuery || window.jQuery );

// -----  ----- //

  return jQueryBridget;

}));

/**
 * EvEmitter v1.1.0
 * Lil' event emitter
 * MIT License
 */

/* jshint unused: true, undef: true, strict: true */

( function( global, factory ) {
  // universal module definition
  /* jshint strict: false */ /* globals define, module, window */
  if ( typeof define == 'function' && define.amd ) {
    // AMD - RequireJS
    define( 'ev-emitter/ev-emitter',factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS - Browserify, Webpack
    module.exports = factory();
  } else {
    // Browser globals
    global.EvEmitter = factory();
  }

}( typeof window != 'undefined' ? window : this, function() {



  function EvEmitter() {}

  var proto = EvEmitter.prototype;

  proto.on = function( eventName, listener ) {
    if ( !eventName || !listener ) {
      return;
    }
    // set events hash
    var events = this._events = this._events || {};
    // set listeners array
    var listeners = events[ eventName ] = events[ eventName ] || [];
    // only add once
    if ( listeners.indexOf( listener ) == -1 ) {
      listeners.push( listener );
    }

    return this;
  };

  proto.once = function( eventName, listener ) {
    if ( !eventName || !listener ) {
      return;
    }
    // add event
    this.on( eventName, listener );
    // set once flag
    // set onceEvents hash
    var onceEvents = this._onceEvents = this._onceEvents || {};
    // set onceListeners object
    var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
    // set flag
    onceListeners[ listener ] = true;

    return this;
  };

  proto.off = function( eventName, listener ) {
    var listeners = this._events && this._events[ eventName ];
    if ( !listeners || !listeners.length ) {
      return;
    }
    var index = listeners.indexOf( listener );
    if ( index != -1 ) {
      listeners.splice( index, 1 );
    }

    return this;
  };

  proto.emitEvent = function( eventName, args ) {
    var listeners = this._events && this._events[ eventName ];
    if ( !listeners || !listeners.length ) {
      return;
    }
    // copy over to avoid interference if .off() in listener
    listeners = listeners.slice(0);
    args = args || [];
    // once stuff
    var onceListeners = this._onceEvents && this._onceEvents[ eventName ];

    for ( var i=0; i < listeners.length; i++ ) {
      var listener = listeners[i]
      var isOnce = onceListeners && onceListeners[ listener ];
      if ( isOnce ) {
        // remove listener
        // remove before trigger to prevent recursion
        this.off( eventName, listener );
        // unset once flag
        delete onceListeners[ listener ];
      }
      // trigger listener
      listener.apply( this, args );
    }

    return this;
  };

  proto.allOff = function() {
    delete this._events;
    delete this._onceEvents;
  };

  return EvEmitter;

}));

/*!
 * getSize v2.0.3
 * measure size of elements
 * MIT license
 */

/* jshint browser: true, strict: true, undef: true, unused: true */
/* globals console: false */

( function( window, factory ) {
  /* jshint strict: false */ /* globals define, module */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'get-size/get-size',factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory();
  } else {
    // browser global
    window.getSize = factory();
  }

})( window, function factory() {
  'use strict';

// -------------------------- helpers -------------------------- //

// get a number from a string, not a percentage
  function getStyleSize( value ) {
    var num = parseFloat( value );
    // not a percent like '100%', and a number
    var isValid = value.indexOf('%') == -1 && !isNaN( num );
    return isValid && num;
  }

  function noop() {}

  var logError = typeof console == 'undefined' ? noop :
      function( message ) {
        console.error( message );
      };

// -------------------------- measurements -------------------------- //

  var measurements = [
    'paddingLeft',
    'paddingRight',
    'paddingTop',
    'paddingBottom',
    'marginLeft',
    'marginRight',
    'marginTop',
    'marginBottom',
    'borderLeftWidth',
    'borderRightWidth',
    'borderTopWidth',
    'borderBottomWidth'
  ];

  var measurementsLength = measurements.length;

  function getZeroSize() {
    var size = {
      width: 0,
      height: 0,
      innerWidth: 0,
      innerHeight: 0,
      outerWidth: 0,
      outerHeight: 0
    };
    for ( var i=0; i < measurementsLength; i++ ) {
      var measurement = measurements[i];
      size[ measurement ] = 0;
    }
    return size;
  }

// -------------------------- getStyle -------------------------- //

  /**
   * getStyle, get style of element, check for Firefox bug
   * https://bugzilla.mozilla.org/show_bug.cgi?id=548397
   */
  function getStyle( elem ) {
    var style = getComputedStyle( elem );
    if ( !style ) {
      logError( 'Style returned ' + style +
          '. Are you running this code in a hidden iframe on Firefox? ' +
          'See https://bit.ly/getsizebug1' );
    }
    return style;
  }

// -------------------------- setup -------------------------- //

  var isSetup = false;

  var isBoxSizeOuter;

  /**
   * setup
   * check isBoxSizerOuter
   * do on first getSize() rather than on page load for Firefox bug
   */
  function setup() {
    // setup once
    if ( isSetup ) {
      return;
    }
    isSetup = true;

    // -------------------------- box sizing -------------------------- //

    /**
     * Chrome & Safari measure the outer-width on style.width on border-box elems
     * IE11 & Firefox<29 measures the inner-width
     */
    var div = document.createElement('div');
    div.style.width = '200px';
    div.style.padding = '1px 2px 3px 4px';
    div.style.borderStyle = 'solid';
    div.style.borderWidth = '1px 2px 3px 4px';
    div.style.boxSizing = 'border-box';

    var body = document.body || document.documentElement;
    body.appendChild( div );
    var style = getStyle( div );
    // round value for browser zoom. desandro/masonry#928
    isBoxSizeOuter = Math.round( getStyleSize( style.width ) ) == 200;
    getSize.isBoxSizeOuter = isBoxSizeOuter;

    body.removeChild( div );
  }

// -------------------------- getSize -------------------------- //

  function getSize( elem ) {
    setup();

    // use querySeletor if elem is string
    if ( typeof elem == 'string' ) {
      elem = document.querySelector( elem );
    }

    // do not proceed on non-objects
    if ( !elem || typeof elem != 'object' || !elem.nodeType ) {
      return;
    }

    var style = getStyle( elem );

    // if hidden, everything is 0
    if ( style.display == 'none' ) {
      return getZeroSize();
    }

    var size = {};
    size.width = elem.offsetWidth;
    size.height = elem.offsetHeight;

    var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box';

    // get all measurements
    for ( var i=0; i < measurementsLength; i++ ) {
      var measurement = measurements[i];
      var value = style[ measurement ];
      var num = parseFloat( value );
      // any 'auto', 'medium' value will be 0
      size[ measurement ] = !isNaN( num ) ? num : 0;
    }

    var paddingWidth = size.paddingLeft + size.paddingRight;
    var paddingHeight = size.paddingTop + size.paddingBottom;
    var marginWidth = size.marginLeft + size.marginRight;
    var marginHeight = size.marginTop + size.marginBottom;
    var borderWidth = size.borderLeftWidth + size.borderRightWidth;
    var borderHeight = size.borderTopWidth + size.borderBottomWidth;

    var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;

    // overwrite width and height if we can get it from style
    var styleWidth = getStyleSize( style.width );
    if ( styleWidth !== false ) {
      size.width = styleWidth +
          // add padding and border unless it's already including it
          ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth );
    }

    var styleHeight = getStyleSize( style.height );
    if ( styleHeight !== false ) {
      size.height = styleHeight +
          // add padding and border unless it's already including it
          ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight );
    }

    size.innerWidth = size.width - ( paddingWidth + borderWidth );
    size.innerHeight = size.height - ( paddingHeight + borderHeight );

    size.outerWidth = size.width + marginWidth;
    size.outerHeight = size.height + marginHeight;

    return size;
  }

  return getSize;

});

/**
 * matchesSelector v2.0.2
 * matchesSelector( element, '.selector' )
 * MIT license
 */

/*jshint browser: true, strict: true, undef: true, unused: true */

( function( window, factory ) {
  /*global define: false, module: false */
  'use strict';
  // universal module definition
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'desandro-matches-selector/matches-selector',factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory();
  } else {
    // browser global
    window.matchesSelector = factory();
  }

}( window, function factory() {
  'use strict';

  var matchesMethod = ( function() {
    var ElemProto = window.Element.prototype;
    // check for the standard method name first
    if ( ElemProto.matches ) {
      return 'matches';
    }
    // check un-prefixed
    if ( ElemProto.matchesSelector ) {
      return 'matchesSelector';
    }
    // check vendor prefixes
    var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];

    for ( var i=0; i < prefixes.length; i++ ) {
      var prefix = prefixes[i];
      var method = prefix + 'MatchesSelector';
      if ( ElemProto[ method ] ) {
        return method;
      }
    }
  })();

  return function matchesSelector( elem, selector ) {
    return elem[ matchesMethod ]( selector );
  };

}));

/**
 * Fizzy UI utils v2.0.7
 * MIT license
 */

/*jshint browser: true, undef: true, unused: true, strict: true */

( function( window, factory ) {
  // universal module definition
  /*jshint strict: false */ /*globals define, module, require */

  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'fizzy-ui-utils/utils',[
      'desandro-matches-selector/matches-selector'
    ], function( matchesSelector ) {
      return factory( window, matchesSelector );
    });
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('desandro-matches-selector')
    );
  } else {
    // browser global
    window.fizzyUIUtils = factory(
        window,
        window.matchesSelector
    );
  }

}( window, function factory( window, matchesSelector ) {



  var utils = {};

// ----- extend ----- //

// extends objects
  utils.extend = function( a, b ) {
    for ( var prop in b ) {
      a[ prop ] = b[ prop ];
    }
    return a;
  };

// ----- modulo ----- //

  utils.modulo = function( num, div ) {
    return ( ( num % div ) + div ) % div;
  };

// ----- makeArray ----- //

  var arraySlice = Array.prototype.slice;

// turn element or nodeList into an array
  utils.makeArray = function( obj ) {
    if ( Array.isArray( obj ) ) {
      // use object if already an array
      return obj;
    }
    // return empty array if undefined or null. #6
    if ( obj === null || obj === undefined ) {
      return [];
    }

    var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
    if ( isArrayLike ) {
      // convert nodeList to array
      return arraySlice.call( obj );
    }

    // array of single index
    return [ obj ];
  };

// ----- removeFrom ----- //

  utils.removeFrom = function( ary, obj ) {
    var index = ary.indexOf( obj );
    if ( index != -1 ) {
      ary.splice( index, 1 );
    }
  };

// ----- getParent ----- //

  utils.getParent = function( elem, selector ) {
    while ( elem.parentNode && elem != document.body ) {
      elem = elem.parentNode;
      if ( matchesSelector( elem, selector ) ) {
        return elem;
      }
    }
  };

// ----- getQueryElement ----- //

// use element as selector string
  utils.getQueryElement = function( elem ) {
    if ( typeof elem == 'string' ) {
      return document.querySelector( elem );
    }
    return elem;
  };

// ----- handleEvent ----- //

// enable .ontype to trigger from .addEventListener( elem, 'type' )
  utils.handleEvent = function( event ) {
    var method = 'on' + event.type;
    if ( this[ method ] ) {
      this[ method ]( event );
    }
  };

// ----- filterFindElements ----- //

  utils.filterFindElements = function( elems, selector ) {
    // make array of elems
    elems = utils.makeArray( elems );
    var ffElems = [];
    var isElement = function (elem) {
      return (
        typeof HTMLElement === "object" ? elem instanceof HTMLElement : elem && typeof elem === "object" && elem !== null && elem.nodeType === 1 && typeof elem.nodeName === "string"
      );
    };
    elems.forEach( function( elem ) {
      // check that elem is an actual element
      if ( !( isElement(elem) ) ) {
        return;
      }
      // add elem if no selector
      if ( !selector ) {
        ffElems.push( elem );
        return;
      }
      // filter & find items if we have a selector
      // filter
      if ( matchesSelector( elem, selector ) ) {
        ffElems.push( elem );
      }
      // find children
      var childElems = elem.querySelectorAll( selector );
      // concat childElems to filterFound array
      for ( var i=0; i < childElems.length; i++ ) {
        ffElems.push( childElems[i] );
      }
    });

    return ffElems;
  };

// ----- debounceMethod ----- //

  utils.debounceMethod = function( _class, methodName, threshold ) {
    threshold = threshold || 100;
    // original method
    var method = _class.prototype[ methodName ];
    var timeoutName = methodName + 'Timeout';

    _class.prototype[ methodName ] = function() {
      var timeout = this[ timeoutName ];
      clearTimeout( timeout );

      var args = arguments;
      var _this = this;
      this[ timeoutName ] = setTimeout( function() {
        method.apply( _this, args );
        delete _this[ timeoutName ];
      }, threshold );
    };
  };

// ----- docReady ----- //

  utils.docReady = function( callback ) {
    var readyState = document.readyState;
    if ( readyState == 'complete' || readyState == 'interactive' ) {
      // do async to allow for other scripts to run. metafizzy/flickity#441
      setTimeout( callback );
    } else {
      document.addEventListener( 'DOMContentLoaded', callback );
    }
  };

// ----- htmlInit ----- //

// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
  utils.toDashed = function( str ) {
    return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
      return $1 + '-' + $2;
    }).toLowerCase();
  };

  var console = window.console;
  /**
   * allow user to initialize classes via [data-namespace] or .js-namespace class
   * htmlInit( Widget, 'widgetName' )
   * options are parsed from data-namespace-options
   */
  utils.htmlInit = function( WidgetClass, namespace ) {
    utils.docReady( function() {
      var dashedNamespace = utils.toDashed( namespace );
      var dataAttr = 'data-' + dashedNamespace;
      var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' );
      var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace );
      var elems = utils.makeArray( dataAttrElems )
          .concat( utils.makeArray( jsDashElems ) );
      var dataOptionsAttr = dataAttr + '-options';
      var jQuery = window.jQuery;

      elems.forEach( function( elem ) {
        var attr = elem.getAttribute( dataAttr ) ||
            elem.getAttribute( dataOptionsAttr );
        var options;
        try {
          options = attr && JSON.parse( attr );
        } catch ( error ) {
          // log error, do not initialize
          if ( console ) {
            console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className +
                ': ' + error );
          }
          return;
        }
        // initialize
        var instance = new WidgetClass( elem, options );
        // make available via $().data('namespace')
        if ( jQuery ) {
          jQuery.data( elem, namespace, instance );
        }
      });

    });
  };

// -----  ----- //

  return utils;

}));

/**
 * Outlayer Item
 */

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /* globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD - RequireJS
    define( 'outlayer/item',[
          'ev-emitter/ev-emitter',
          'get-size/get-size'
        ],
        factory
    );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS - Browserify, Webpack
    module.exports = factory(
        require('ev-emitter'),
        require('get-size')
    );
  } else {
    // browser global
    window.Outlayer = {};
    window.Outlayer.Item = factory(
        window.EvEmitter,
        window.getSize
    );
  }

}( window, function factory( EvEmitter, getSize ) {
  'use strict';

// ----- helpers ----- //

  function isEmptyObj( obj ) {
    for ( var prop in obj ) {
      return false;
    }
    prop = null;
    return true;
  }

// -------------------------- CSS3 support -------------------------- //


  var docElemStyle = document.documentElement.style;

  var transitionProperty = typeof docElemStyle.transition == 'string' ?
      'transition' : 'WebkitTransition';
  var transformProperty = typeof docElemStyle.transform == 'string' ?
      'transform' : 'WebkitTransform';

  var transitionEndEvent = {
    WebkitTransition: 'webkitTransitionEnd',
    transition: 'transitionend'
  }[ transitionProperty ];

// cache all vendor properties that could have vendor prefix
  var vendorProperties = {
    transform: transformProperty,
    transition: transitionProperty,
    transitionDuration: transitionProperty + 'Duration',
    transitionProperty: transitionProperty + 'Property',
    transitionDelay: transitionProperty + 'Delay'
  };

// -------------------------- Item -------------------------- //

  function Item( element, layout ) {
    if ( !element ) {
      return;
    }

    this.element = element;
    // parent layout class, i.e. Masonry, Isotope, or Packery
    this.layout = layout;
    this.position = {
      x: 0,
      y: 0
    };

    this._create();
  }

// inherit EvEmitter
  var proto = Item.prototype = Object.create( EvEmitter.prototype );
  proto.constructor = Item;

  proto._create = function() {
    // transition objects
    this._transn = {
      ingProperties: {},
      clean: {},
      onEnd: {}
    };

    this.css({
      position: 'absolute'
    });
  };

// trigger specified handler for event type
  proto.handleEvent = function( event ) {
    var method = 'on' + event.type;
    if ( this[ method ] ) {
      this[ method ]( event );
    }
  };

  proto.getSize = function() {
    this.size = getSize( this.element );
  };

  /**
   * apply CSS styles to element
   * @param {Object} style
   */
  proto.css = function( style ) {
    var elemStyle = this.element.style;

    for ( var prop in style ) {
      // use vendor property if available
      var supportedProp = vendorProperties[ prop ] || prop;
      elemStyle[ supportedProp ] = style[ prop ];
    }
  };

  // measure position, and sets it
  proto.getPosition = function() {
    var style = getComputedStyle( this.element );
    var isOriginLeft = this.layout._getOption('originLeft');
    var isOriginTop = this.layout._getOption('originTop');
    var xValue = style[ isOriginLeft ? 'left' : 'right' ];
    var yValue = style[ isOriginTop ? 'top' : 'bottom' ];
    var x = parseFloat( xValue );
    var y = parseFloat( yValue );
    // convert percent to pixels
    var layoutSize = this.layout.size;
    if ( xValue.indexOf('%') != -1 ) {
      x = ( x / 100 ) * layoutSize.width;
    }
    if ( yValue.indexOf('%') != -1 ) {
      y = ( y / 100 ) * layoutSize.height;
    }
    // clean up 'auto' or other non-integer values
    x = isNaN( x ) ? 0 : x;
    y = isNaN( y ) ? 0 : y;
    // remove padding from measurement
    x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight;
    y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom;

    this.position.x = x;
    this.position.y = y;
  };

// set settled position, apply padding
  proto.layoutPosition = function() {
    var layoutSize = this.layout.size;
    var style = {};
    var isOriginLeft = this.layout._getOption('originLeft');
    var isOriginTop = this.layout._getOption('originTop');

    // x
    var xPadding = isOriginLeft ? 'paddingLeft' : 'paddingRight';
    var xProperty = isOriginLeft ? 'left' : 'right';
    var xResetProperty = isOriginLeft ? 'right' : 'left';

    var x = this.position.x + layoutSize[ xPadding ];
    // set in percentage or pixels
    style[ xProperty ] = this.getXValue( x );
    // reset other property
    style[ xResetProperty ] = '';

    // y
    var yPadding = isOriginTop ? 'paddingTop' : 'paddingBottom';
    var yProperty = isOriginTop ? 'top' : 'bottom';
    var yResetProperty = isOriginTop ? 'bottom' : 'top';

    var y = this.position.y + layoutSize[ yPadding ];
    // set in percentage or pixels
    style[ yProperty ] = this.getYValue( y );
    // reset other property
    style[ yResetProperty ] = '';

    this.css( style );
    this.emitEvent( 'layout', [ this ] );
  };

  proto.getXValue = function( x ) {
    var isHorizontal = this.layout._getOption('horizontal');
    return this.layout.options.percentPosition && !isHorizontal ?
        ( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px';
  };

  proto.getYValue = function( y ) {
    var isHorizontal = this.layout._getOption('horizontal');
    return this.layout.options.percentPosition && isHorizontal ?
        ( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px';
  };

  proto._transitionTo = function( x, y ) {
    this.getPosition();
    // get current x & y from top/left
    var curX = this.position.x;
    var curY = this.position.y;

    var didNotMove = x == this.position.x && y == this.position.y;

    // save end position
    this.setPosition( x, y );

    // if did not move and not transitioning, just go to layout
    if ( didNotMove && !this.isTransitioning ) {
      this.layoutPosition();
      return;
    }

    var transX = x - curX;
    var transY = y - curY;
    var transitionStyle = {};
    transitionStyle.transform = this.getTranslate( transX, transY );

    this.transition({
      to: transitionStyle,
      onTransitionEnd: {
        transform: this.layoutPosition
      },
      isCleaning: true
    });
  };

  proto.getTranslate = function( x, y ) {
    // flip cooridinates if origin on right or bottom
    var isOriginLeft = this.layout._getOption('originLeft');
    var isOriginTop = this.layout._getOption('originTop');
    x = isOriginLeft ? x : -x;
    y = isOriginTop ? y : -y;
    return 'translate3d(' + x + 'px, ' + y + 'px, 0)';
  };

// non transition + transform support
  proto.goTo = function( x, y ) {
    this.setPosition( x, y );
    this.layoutPosition();
  };

  proto.moveTo = proto._transitionTo;

  proto.setPosition = function( x, y ) {
    this.position.x = parseFloat( x );
    this.position.y = parseFloat( y );
  };

// ----- transition ----- //

  /**
   * @param {Object} style - CSS
   * @param {Function} onTransitionEnd
   */

// non transition, just trigger callback
  proto._nonTransition = function( args ) {
    this.css( args.to );
    if ( args.isCleaning ) {
      this._removeStyles( args.to );
    }
    for ( var prop in args.onTransitionEnd ) {
      args.onTransitionEnd[ prop ].call( this );
    }
  };

  /**
   * proper transition
   * @param {Object} args - arguments
   *   @param {Object} to - style to transition to
   *   @param {Object} from - style to start transition from
   *   @param {Boolean} isCleaning - removes transition styles after transition
   *   @param {Function} onTransitionEnd - callback
   */
  proto.transition = function( args ) {
    // redirect to nonTransition if no transition duration
    if ( !parseFloat( this.layout.options.transitionDuration ) ) {
      this._nonTransition( args );
      return;
    }

    var _transition = this._transn;
    // keep track of onTransitionEnd callback by css property
    for ( var prop in args.onTransitionEnd ) {
      _transition.onEnd[ prop ] = args.onTransitionEnd[ prop ];
    }
    // keep track of properties that are transitioning
    for ( prop in args.to ) {
      _transition.ingProperties[ prop ] = true;
      // keep track of properties to clean up when transition is done
      if ( args.isCleaning ) {
        _transition.clean[ prop ] = true;
      }
    }

    // set from styles
    if ( args.from ) {
      this.css( args.from );
      // force redraw. http://blog.alexmaccaw.com/css-transitions
      var h = this.element.offsetHeight;
      // hack for JSHint to hush about unused var
      h = null;
    }
    // enable transition
    this.enableTransition( args.to );
    // set styles that are transitioning
    this.css( args.to );

    this.isTransitioning = true;

  };

// dash before all cap letters, including first for
// WebkitTransform => -webkit-transform
  function toDashedAll( str ) {
    return str.replace( /([A-Z])/g, function( $1 ) {
      return '-' + $1.toLowerCase();
    });
  }

  var transitionProps = 'opacity,' + toDashedAll( transformProperty );

  proto.enableTransition = function(/* style */) {
    // HACK changing transitionProperty during a transition
    // will cause transition to jump
    if ( this.isTransitioning ) {
      return;
    }

    // make `transition: foo, bar, baz` from style object
    // HACK un-comment this when enableTransition can work
    // while a transition is happening
    // var transitionValues = [];
    // for ( var prop in style ) {
    //   // dash-ify camelCased properties like WebkitTransition
    //   prop = vendorProperties[ prop ] || prop;
    //   transitionValues.push( toDashedAll( prop ) );
    // }
    // munge number to millisecond, to match stagger
    var duration = this.layout.options.transitionDuration;
    duration = typeof duration == 'number' ? duration + 'ms' : duration;
    // enable transition styles
    this.css({
      transitionProperty: transitionProps,
      transitionDuration: duration,
      transitionDelay: this.staggerDelay || 0
    });
    // listen for transition end event
    this.element.addEventListener( transitionEndEvent, this, false );
  };

// ----- events ----- //

  proto.onwebkitTransitionEnd = function( event ) {
    this.ontransitionend( event );
  };

  proto.onotransitionend = function( event ) {
    this.ontransitionend( event );
  };

// properties that I munge to make my life easier
  var dashedVendorProperties = {
    '-webkit-transform': 'transform'
  };

  proto.ontransitionend = function( event ) {
    // disregard bubbled events from children
    if ( event.target !== this.element ) {
      return;
    }
    var _transition = this._transn;
    // get property name of transitioned property, convert to prefix-free
    var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName;

    // remove property that has completed transitioning
    delete _transition.ingProperties[ propertyName ];
    // check if any properties are still transitioning
    if ( isEmptyObj( _transition.ingProperties ) ) {
      // all properties have completed transitioning
      this.disableTransition();
    }
    // clean style
    if ( propertyName in _transition.clean ) {
      // clean up style
      this.element.style[ event.propertyName ] = '';
      delete _transition.clean[ propertyName ];
    }
    // trigger onTransitionEnd callback
    if ( propertyName in _transition.onEnd ) {
      var onTransitionEnd = _transition.onEnd[ propertyName ];
      onTransitionEnd.call( this );
      delete _transition.onEnd[ propertyName ];
    }

    this.emitEvent( 'transitionEnd', [ this ] );
  };

  proto.disableTransition = function() {
    this.removeTransitionStyles();
    this.element.removeEventListener( transitionEndEvent, this, false );
    this.isTransitioning = false;
  };

  /**
   * removes style property from element
   * @param {Object} style
   **/
  proto._removeStyles = function( style ) {
    // clean up transition styles
    var cleanStyle = {};
    for ( var prop in style ) {
      cleanStyle[ prop ] = '';
    }
    this.css( cleanStyle );
  };

  var cleanTransitionStyle = {
    transitionProperty: '',
    transitionDuration: '',
    transitionDelay: ''
  };

  proto.removeTransitionStyles = function() {
    // remove transition
    this.css( cleanTransitionStyle );
  };

// ----- stagger ----- //

  proto.stagger = function( delay ) {
    delay = isNaN( delay ) ? 0 : delay;
    this.staggerDelay = delay + 'ms';
  };

// ----- show/hide/remove ----- //

// remove element from DOM
  proto.removeElem = function() {
    this.element.parentNode.removeChild( this.element );
    // remove display: none
    this.css({ display: '' });
    this.emitEvent( 'remove', [ this ] );
  };

  proto.remove = function() {
    // just remove element if no transition support or no transition
    if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) {
      this.removeElem();
      return;
    }

    // start transition
    this.once( 'transitionEnd', function() {
      this.removeElem();
    });
    this.hide();
  };

  proto.reveal = function() {
    delete this.isHidden;
    // remove display: none
    this.css({ display: '' });

    var options = this.layout.options;

    var onTransitionEnd = {};
    var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle');
    onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd;

    this.transition({
      from: options.hiddenStyle,
      to: options.visibleStyle,
      isCleaning: true,
      onTransitionEnd: onTransitionEnd
    });
  };

  proto.onRevealTransitionEnd = function() {
    // check if still visible
    // during transition, item may have been hidden
    if ( !this.isHidden ) {
      this.emitEvent('reveal');
    }
  };

  /**
   * get style property use for hide/reveal transition end
   * @param {String} styleProperty - hiddenStyle/visibleStyle
   * @returns {String}
   */
  proto.getHideRevealTransitionEndProperty = function( styleProperty ) {
    var optionStyle = this.layout.options[ styleProperty ];
    // use opacity
    if ( optionStyle.opacity ) {
      return 'opacity';
    }
    // get first property
    for ( var prop in optionStyle ) {
      return prop;
    }
  };

  proto.hide = function() {
    // set flag
    this.isHidden = true;
    // remove display: none
    this.css({ display: '' });

    var options = this.layout.options;

    var onTransitionEnd = {};
    var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle');
    onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd;

    this.transition({
      from: options.visibleStyle,
      to: options.hiddenStyle,
      // keep hidden stuff hidden
      isCleaning: true,
      onTransitionEnd: onTransitionEnd
    });
  };

  proto.onHideTransitionEnd = function() {
    // check if still hidden
    // during transition, item may have been un-hidden
    if ( this.isHidden ) {
      this.css({ display: 'none' });
      this.emitEvent('hide');
    }
  };

  proto.destroy = function() {
    this.css({
      position: '',
      left: '',
      right: '',
      top: '',
      bottom: '',
      transition: '',
      transform: ''
    });
  };

  return Item;

}));

/*!
 * Outlayer v2.1.1
 * the brains and guts of a layout library
 * MIT license
 */

( function( window, factory ) {
  'use strict';
  // universal module definition
  /* jshint strict: false */ /* globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD - RequireJS
    define( 'outlayer/outlayer',[
          'ev-emitter/ev-emitter',
          'get-size/get-size',
          'fizzy-ui-utils/utils',
          './item'
        ],
        function( EvEmitter, getSize, utils, Item ) {
          return factory( window, EvEmitter, getSize, utils, Item);
        }
    );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS - Browserify, Webpack
    module.exports = factory(
        window,
        require('ev-emitter'),
        require('get-size'),
        require('fizzy-ui-utils'),
        require('./item')
    );
  } else {
    // browser global
    window.Outlayer = factory(
        window,
        window.EvEmitter,
        window.getSize,
        window.fizzyUIUtils,
        window.Outlayer.Item
    );
  }

}( window, function factory( window, EvEmitter, getSize, utils, Item ) {
  'use strict';

// ----- vars ----- //

  var console = window.console;
  var jQuery = window.jQuery;
  var noop = function() {};

// -------------------------- Outlayer -------------------------- //

// globally unique identifiers
  var GUID = 0;
// internal store of all Outlayer intances
  var instances = {};


  /**
   * @param {Element, String} element
   * @param {Object} options
   * @constructor
   */
  function Outlayer( element, options ) {
    var queryElement = utils.getQueryElement( element );
    if ( !queryElement ) {
      if ( console ) {
        console.error( 'Bad element for ' + this.constructor.namespace +
            ': ' + ( queryElement || element ) );
      }
      return;
    }
    this.element = queryElement;
    // add jQuery
    if ( jQuery ) {
      this.$element = jQuery( this.element );
    }

    // options
    this.options = utils.extend( {}, this.constructor.defaults );
    this.option( options );

    // add id for Outlayer.getFromElement
    var id = ++GUID;
    this.element.outlayerGUID = id; // expando
    instances[ id ] = this; // associate via id

    // kick it off
    this._create();

    var isInitLayout = this._getOption('initLayout');
    if ( isInitLayout ) {
      this.layout();
    }
  }

// settings are for internal use only
  Outlayer.namespace = 'outlayer';
  Outlayer.Item = Item;

// default options
  Outlayer.defaults = {
    containerStyle: {
      position: 'relative'
    },
    initLayout: true,
    originLeft: true,
    originTop: true,
    resize: true,
    resizeContainer: true,
    // item options
    transitionDuration: '0.4s',
    hiddenStyle: {
      opacity: 0,
      transform: 'scale(0.001)'
    },
    visibleStyle: {
      opacity: 1,
      transform: 'scale(1)'
    }
  };

  var proto = Outlayer.prototype;
// inherit EvEmitter
  utils.extend( proto, EvEmitter.prototype );

  /**
   * set options
   * @param {Object} opts
   */
  proto.option = function( opts ) {
    utils.extend( this.options, opts );
  };

  /**
   * get backwards compatible option value, check old name
   */
  proto._getOption = function( option ) {
    var oldOption = this.constructor.compatOptions[ option ];
    return oldOption && this.options[ oldOption ] !== undefined ?
        this.options[ oldOption ] : this.options[ option ];
  };

  Outlayer.compatOptions = {
    // currentName: oldName
    initLayout: 'isInitLayout',
    horizontal: 'isHorizontal',
    layoutInstant: 'isLayoutInstant',
    originLeft: 'isOriginLeft',
    originTop: 'isOriginTop',
    resize: 'isResizeBound',
    resizeContainer: 'isResizingContainer'
  };

  proto._create = function() {
    // get items from children
    this.reloadItems();
    // elements that affect layout, but are not laid out
    this.stamps = [];
    this.stamp( this.options.stamp );
    // set container style
    utils.extend( this.element.style, this.options.containerStyle );

    // bind resize method
    var canBindResize = this._getOption('resize');
    if ( canBindResize ) {
      this.bindResize();
    }
  };

// goes through all children again and gets bricks in proper order
  proto.reloadItems = function() {
    // collection of item elements
    this.items = this._itemize( this.element.children );
  };


  /**
   * turn elements into Outlayer.Items to be used in layout
   * @param {Array or NodeList or HTMLElement} elems
   * @returns {Array} items - collection of new Outlayer Items
   */
  proto._itemize = function( elems ) {

    var itemElems = this._filterFindItemElements( elems );
    var Item = this.constructor.Item;

    // create new Outlayer Items for collection
    var items = [];
    for ( var i=0; i < itemElems.length; i++ ) {
      var elem = itemElems[i];
      var item = new Item( elem, this );
      items.push( item );
    }

    return items;
  };

  /**
   * get item elements to be used in layout
   * @param {Array or NodeList or HTMLElement} elems
   * @returns {Array} items - item elements
   */
  proto._filterFindItemElements = function( elems ) {
    return utils.filterFindElements( elems, this.options.itemSelector );
  };

  /**
   * getter method for getting item elements
   * @returns {Array} elems - collection of item elements
   */
  proto.getItemElements = function() {
    return this.items.map( function( item ) {
      return item.element;
    });
  };

// ----- init & layout ----- //

  /**
   * lays out all items
   */
  proto.layout = function() {
    this._resetLayout();
    this._manageStamps();

    // don't animate first layout
    var layoutInstant = this._getOption('layoutInstant');
    var isInstant = layoutInstant !== undefined ?
        layoutInstant : !this._isLayoutInited;
    this.layoutItems( this.items, isInstant );

    // flag for initalized
    this._isLayoutInited = true;
  };

// _init is alias for layout
  proto._init = proto.layout;

  /**
   * logic before any new layout
   */
  proto._resetLayout = function() {
    this.getSize();
  };


  proto.getSize = function() {
    this.size = getSize( this.element );
  };

  /**
   * get measurement from option, for columnWidth, rowHeight, gutter
   * if option is String -> get element from selector string, & get size of element
   * if option is Element -> get size of element
   * else use option as a number
   *
   * @param {String} measurement
   * @param {String} size - width or height
   * @private
   */
  proto._getMeasurement = function( measurement, size ) {
    var option = this.options[ measurement ];
    var elem;
    if ( !option ) {
      // default to 0
      this[ measurement ] = 0;
    } else {
      // use option as an element
      if ( typeof option == 'string' ) {
        elem = this.element.querySelector( option );
      } else if ( option instanceof HTMLElement ) {
        elem = option;
      }
      // use size of element, if element
      this[ measurement ] = elem ? getSize( elem )[ size ] : option;
    }
  };

  /**
   * layout a collection of item elements
   * @api public
   */
  proto.layoutItems = function( items, isInstant ) {
    items = this._getItemsForLayout( items );

    this._layoutItems( items, isInstant );

    this._postLayout();
  };

  /**
   * get the items to be laid out
   * you may want to skip over some items
   * @param {Array} items
   * @returns {Array} items
   */
  proto._getItemsForLayout = function( items ) {
    return items.filter( function( item ) {
      return !item.isIgnored;
    });
  };

  /**
   * layout items
   * @param {Array} items
   * @param {Boolean} isInstant
   */
  proto._layoutItems = function( items, isInstant ) {
    this._emitCompleteOnItems( 'layout', items );

    if ( !items || !items.length ) {
      // no items, emit event with empty array
      return;
    }

    var queue = [];

    items.forEach( function( item ) {
      // get x/y object from method
      var position = this._getItemLayoutPosition( item );
      // enqueue
      position.item = item;
      position.isInstant = isInstant || item.isLayoutInstant;
      queue.push( position );
    }, this );

    this._processLayoutQueue( queue );
  };

  /**
   * get item layout position
   * @param {Outlayer.Item} item
   * @returns {Object} x and y position
   */
  proto._getItemLayoutPosition = function( /* item */ ) {
    return {
      x: 0,
      y: 0
    };
  };

  /**
   * iterate over array and position each item
   * Reason being - separating this logic prevents 'layout invalidation'
   * thx @paul_irish
   * @param {Array} queue
   */
  proto._processLayoutQueue = function( queue ) {
    this.updateStagger();
    queue.forEach( function( obj, i ) {
      this._positionItem( obj.item, obj.x, obj.y, obj.isInstant, i );
    }, this );
  };

// set stagger from option in milliseconds number
  proto.updateStagger = function() {
    var stagger = this.options.stagger;
    if ( stagger === null || stagger === undefined ) {
      this.stagger = 0;
      return;
    }
    this.stagger = getMilliseconds( stagger );
    return this.stagger;
  };

  /**
   * Sets position of item in DOM
   * @param {Outlayer.Item} item
   * @param {Number} x - horizontal position
   * @param {Number} y - vertical position
   * @param {Boolean} isInstant - disables transitions
   */
  proto._positionItem = function( item, x, y, isInstant, i ) {
    if ( isInstant ) {
      // if not transition, just set CSS
      item.goTo( x, y );
    } else {
      item.stagger( i * this.stagger );
      item.moveTo( x, y );
    }
  };

  /**
   * Any logic you want to do after each layout,
   * i.e. size the container
   */
  proto._postLayout = function() {
    this.resizeContainer();
  };

  proto.resizeContainer = function() {
    var isResizingContainer = this._getOption('resizeContainer');
    if ( !isResizingContainer ) {
      return;
    }
    var size = this._getContainerSize();
    if ( size ) {
      this._setContainerMeasure( size.width, true );
      this._setContainerMeasure( size.height, false );
    }
  };

  /**
   * Sets width or height of container if returned
   * @returns {Object} size
   *   @param {Number} width
   *   @param {Number} height
   */
  proto._getContainerSize = noop;

  /**
   * @param {Number} measure - size of width or height
   * @param {Boolean} isWidth
   */
  proto._setContainerMeasure = function( measure, isWidth ) {
    if ( measure === undefined ) {
      return;
    }

    var elemSize = this.size;
    // add padding and border width if border box
    if ( elemSize.isBorderBox ) {
      measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
          elemSize.borderLeftWidth + elemSize.borderRightWidth :
          elemSize.paddingBottom + elemSize.paddingTop +
          elemSize.borderTopWidth + elemSize.borderBottomWidth;
    }

    measure = Math.max( measure, 0 );
    this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px';
  };

  /**
   * emit eventComplete on a collection of items events
   * @param {String} eventName
   * @param {Array} items - Outlayer.Items
   */
  proto._emitCompleteOnItems = function( eventName, items ) {
    var _this = this;
    function onComplete() {
      _this.dispatchEvent( eventName + 'Complete', null, [ items ] );
    }

    var count = items.length;
    if ( !items || !count ) {
      onComplete();
      return;
    }

    var doneCount = 0;
    function tick() {
      doneCount++;
      if ( doneCount == count ) {
        onComplete();
      }
    }

    // bind callback
    items.forEach( function( item ) {
      item.once( eventName, tick );
    });
  };

  /**
   * emits events via EvEmitter and jQuery events
   * @param {String} type - name of event
   * @param {Event} event - original event
   * @param {Array} args - extra arguments
   */
  proto.dispatchEvent = function( type, event, args ) {
    // add original event to arguments
    var emitArgs = event ? [ event ].concat( args ) : args;
    this.emitEvent( type, emitArgs );

    if ( jQuery ) {
      // set this.$element
      this.$element = this.$element || jQuery( this.element );
      if ( event ) {
        // create jQuery event
        var $event = jQuery.Event( event );
        $event.type = type;
        this.$element.trigger( $event, args );
      } else {
        // just trigger with type if no event available
        this.$element.trigger( type, args );
      }
    }
  };

// -------------------------- ignore & stamps -------------------------- //


  /**
   * keep item in collection, but do not lay it out
   * ignored items do not get skipped in layout
   * @param {Element} elem
   */
  proto.ignore = function( elem ) {
    var item = this.getItem( elem );
    if ( item ) {
      item.isIgnored = true;
    }
  };

  /**
   * return item to layout collection
   * @param {Element} elem
   */
  proto.unignore = function( elem ) {
    var item = this.getItem( elem );
    if ( item ) {
      delete item.isIgnored;
    }
  };

  /**
   * adds elements to stamps
   * @param {NodeList, Array, Element, or String} elems
   */
  proto.stamp = function( elems ) {
    elems = this._find( elems );
    if ( !elems ) {
      return;
    }

    this.stamps = this.stamps.concat( elems );
    // ignore
    elems.forEach( this.ignore, this );
  };

  /**
   * removes elements to stamps
   * @param {NodeList, Array, or Element} elems
   */
  proto.unstamp = function( elems ) {
    elems = this._find( elems );
    if ( !elems ){
      return;
    }

    elems.forEach( function( elem ) {
      // filter out removed stamp elements
      utils.removeFrom( this.stamps, elem );
      this.unignore( elem );
    }, this );
  };

  /**
   * finds child elements
   * @param {NodeList, Array, Element, or String} elems
   * @returns {Array} elems
   */
  proto._find = function( elems ) {
    if ( !elems ) {
      return;
    }
    // if string, use argument as selector string
    if ( typeof elems == 'string' ) {
      elems = this.element.querySelectorAll( elems );
    }
    elems = utils.makeArray( elems );
    return elems;
  };

  proto._manageStamps = function() {
    if ( !this.stamps || !this.stamps.length ) {
      return;
    }

    this._getBoundingRect();

    this.stamps.forEach( this._manageStamp, this );
  };

// update boundingLeft / Top
  proto._getBoundingRect = function() {
    // get bounding rect for container element
    var boundingRect = this.element.getBoundingClientRect();
    var size = this.size;
    this._boundingRect = {
      left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
      top: boundingRect.top + size.paddingTop + size.borderTopWidth,
      right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ),
      bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth )
    };
  };

  /**
   * @param {Element} stamp
   **/
  proto._manageStamp = noop;

  /**
   * get x/y position of element relative to container element
   * @param {Element} elem
   * @returns {Object} offset - has left, top, right, bottom
   */
  proto._getElementOffset = function( elem ) {
    var boundingRect = elem.getBoundingClientRect();
    var thisRect = this._boundingRect;
    var size = getSize( elem );
    var offset = {
      left: boundingRect.left - thisRect.left - size.marginLeft,
      top: boundingRect.top - thisRect.top - size.marginTop,
      right: thisRect.right - boundingRect.right - size.marginRight,
      bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
    };
    return offset;
  };

// -------------------------- resize -------------------------- //

// enable event handlers for listeners
// i.e. resize -> onresize
  proto.handleEvent = utils.handleEvent;

  /**
   * Bind layout to window resizing
   */
  proto.bindResize = function() {
    window.addEventListener( 'resize', this );
    this.isResizeBound = true;
  };

  /**
   * Unbind layout to window resizing
   */
  proto.unbindResize = function() {
    window.removeEventListener( 'resize', this );
    this.isResizeBound = false;
  };

  proto.onresize = function() {
    this.resize();
  };

  utils.debounceMethod( Outlayer, 'onresize', 100 );

  proto.resize = function() {
    // don't trigger if size did not change
    // or if resize was unbound. See #9
    if ( !this.isResizeBound || !this.needsResizeLayout() ) {
      return;
    }

    this.layout();
  };

  /**
   * check if layout is needed post layout
   * @returns Boolean
   */
  proto.needsResizeLayout = function() {
    var size = getSize( this.element );
    // check that this.size and size are there
    // IE8 triggers resize on body size change, so they might not be
    var hasSizes = this.size && size;
    return hasSizes && size.innerWidth !== this.size.innerWidth;
  };

// -------------------------- methods -------------------------- //

  /**
   * add items to Outlayer instance
   * @param {Array or NodeList or Element} elems
   * @returns {Array} items - Outlayer.Items
   **/
  proto.addItems = function( elems ) {
    var items = this._itemize( elems );
    // add items to collection
    if ( items.length ) {
      this.items = this.items.concat( items );
    }
    return items;
  };

  /**
   * Layout newly-appended item elements
   * @param {Array or NodeList or Element} elems
   */
  proto.appended = function( elems ) {
    var items = this.addItems( elems );
    if ( !items.length ) {
      return;
    }
    // layout and reveal just the new items
    this.layoutItems( items, true );
    this.reveal( items );
  };

  /**
   * Layout prepended elements
   * @param {Array or NodeList or Element} elems
   */
  proto.prepended = function( elems ) {
    var items = this._itemize( elems );
    if ( !items.length ) {
      return;
    }
    // add items to beginning of collection
    var previousItems = this.items.slice(0);
    this.items = items.concat( previousItems );
    // start new layout
    this._resetLayout();
    this._manageStamps();
    // layout new stuff without transition
    this.layoutItems( items, true );
    this.reveal( items );
    // layout previous items
    this.layoutItems( previousItems );
  };

  /**
   * reveal a collection of items
   * @param {Array of Outlayer.Items} items
   */
  proto.reveal = function( items ) {
    this._emitCompleteOnItems( 'reveal', items );
    if ( !items || !items.length ) {
      return;
    }
    var stagger = this.updateStagger();
    items.forEach( function( item, i ) {
      item.stagger( i * stagger );
      item.reveal();
    });
  };

  /**
   * hide a collection of items
   * @param {Array of Outlayer.Items} items
   */
  proto.hide = function( items ) {
    this._emitCompleteOnItems( 'hide', items );
    if ( !items || !items.length ) {
      return;
    }
    var stagger = this.updateStagger();
    items.forEach( function( item, i ) {
      item.stagger( i * stagger );
      item.hide();
    });
  };

  /**
   * reveal item elements
   * @param {Array}, {Element}, {NodeList} items
   */
  proto.revealItemElements = function( elems ) {
    var items = this.getItems( elems );
    this.reveal( items );
  };

  /**
   * hide item elements
   * @param {Array}, {Element}, {NodeList} items
   */
  proto.hideItemElements = function( elems ) {
    var items = this.getItems( elems );
    this.hide( items );
  };

  /**
   * get Outlayer.Item, given an Element
   * @param {Element} elem
   * @param {Function} callback
   * @returns {Outlayer.Item} item
   */
  proto.getItem = function( elem ) {
    // loop through items to get the one that matches
    for ( var i=0; i < this.items.length; i++ ) {
      var item = this.items[i];
      if ( item.element == elem ) {
        // return item
        return item;
      }
    }
  };

  /**
   * get collection of Outlayer.Items, given Elements
   * @param {Array} elems
   * @returns {Array} items - Outlayer.Items
   */
  proto.getItems = function( elems ) {
    elems = utils.makeArray( elems );
    var items = [];
    elems.forEach( function( elem ) {
      var item = this.getItem( elem );
      if ( item ) {
        items.push( item );
      }
    }, this );

    return items;
  };

  /**
   * remove element(s) from instance and DOM
   * @param {Array or NodeList or Element} elems
   */
  proto.remove = function( elems ) {
    var removeItems = this.getItems( elems );

    this._emitCompleteOnItems( 'remove', removeItems );

    // bail if no items to remove
    if ( !removeItems || !removeItems.length ) {
      return;
    }

    removeItems.forEach( function( item ) {
      item.remove();
      // remove item from collection
      utils.removeFrom( this.items, item );
    }, this );
  };

// ----- destroy ----- //

// remove and disable Outlayer instance
  proto.destroy = function() {
    // clean up dynamic styles
    var style = this.element.style;
    style.height = '';
    style.position = '';
    style.width = '';
    // destroy items
    this.items.forEach( function( item ) {
      item.destroy();
    });

    this.unbindResize();

    var id = this.element.outlayerGUID;
    delete instances[ id ]; // remove reference to instance by id
    delete this.element.outlayerGUID;
    // remove data for jQuery
    if ( jQuery ) {
      jQuery.removeData( this.element, this.constructor.namespace );
    }

  };

// -------------------------- data -------------------------- //

  /**
   * get Outlayer instance from element
   * @param {Element} elem
   * @returns {Outlayer}
   */
  Outlayer.data = function( elem ) {
    elem = utils.getQueryElement( elem );
    var id = elem && elem.outlayerGUID;
    return id && instances[ id ];
  };


// -------------------------- create Outlayer class -------------------------- //

  /**
   * create a layout class
   * @param {String} namespace
   */
  Outlayer.create = function( namespace, options ) {
    // sub-class Outlayer
    var Layout = subclass( Outlayer );
    // apply new options and compatOptions
    Layout.defaults = utils.extend( {}, Outlayer.defaults );
    utils.extend( Layout.defaults, options );
    Layout.compatOptions = utils.extend( {}, Outlayer.compatOptions  );

    Layout.namespace = namespace;

    Layout.data = Outlayer.data;

    // sub-class Item
    Layout.Item = subclass( Item );

    // -------------------------- declarative -------------------------- //

    utils.htmlInit( Layout, namespace );

    // -------------------------- jQuery bridge -------------------------- //

    // make into jQuery plugin
    if ( jQuery && jQuery.bridget ) {
      jQuery.bridget( namespace, Layout );
    }

    return Layout;
  };

  function subclass( Parent ) {
    function SubClass() {
      Parent.apply( this, arguments );
    }

    SubClass.prototype = Object.create( Parent.prototype );
    SubClass.prototype.constructor = SubClass;

    return SubClass;
  }

// ----- helpers ----- //

// how many milliseconds are in each unit
  var msUnits = {
    ms: 1,
    s: 1000
  };

// munge time-like parameter into millisecond number
// '0.4s' -> 40
  function getMilliseconds( time ) {
    if ( typeof time == 'number' ) {
      return time;
    }
    var matches = time.match( /(^\d*\.?\d*)(\w*)/ );
    var num = matches && matches[1];
    var unit = matches && matches[2];
    if ( !num.length ) {
      return 0;
    }
    num = parseFloat( num );
    var mult = msUnits[ unit ] || 1;
    return num * mult;
  }

// ----- fin ----- //

// back in global
  Outlayer.Item = Item;

  return Outlayer;

}));

/**
 * Isotope Item
 **/

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /*globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'isotope-layout/js/item',[
          'outlayer/outlayer'
        ],
        factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        require('outlayer')
    );
  } else {
    // browser global
    window.Isotope = window.Isotope || {};
    window.Isotope.Item = factory(
        window.Outlayer
    );
  }

}( window, function factory( Outlayer ) {
  'use strict';

// -------------------------- Item -------------------------- //

// sub-class Outlayer Item
  function Item() {
    Outlayer.Item.apply( this, arguments );
  }

  var proto = Item.prototype = Object.create( Outlayer.Item.prototype );

  var _create = proto._create;
  proto._create = function() {
    // assign id, used for original-order sorting
    this.id = this.layout.itemGUID++;
    _create.call( this );
    this.sortData = {};
  };

  proto.updateSortData = function() {
    if ( this.isIgnored ) {
      return;
    }
    // default sorters
    this.sortData.id = this.id;
    // for backward compatibility
    this.sortData['original-order'] = this.id;
    this.sortData.random = Math.random();
    // go thru getSortData obj and apply the sorters
    var getSortData = this.layout.options.getSortData;
    var sorters = this.layout._sorters;
    for ( var key in getSortData ) {
      var sorter = sorters[ key ];
      this.sortData[ key ] = sorter( this.element, this );
    }
  };

  var _destroy = proto.destroy;
  proto.destroy = function() {
    // call super
    _destroy.apply( this, arguments );
    // reset display, #741
    this.css({
      display: ''
    });
  };

  return Item;

}));

/**
 * Isotope LayoutMode
 */

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /*globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'isotope-layout/js/layout-mode',[
          'get-size/get-size',
          'outlayer/outlayer'
        ],
        factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        require('get-size'),
        require('outlayer')
    );
  } else {
    // browser global
    window.Isotope = window.Isotope || {};
    window.Isotope.LayoutMode = factory(
        window.getSize,
        window.Outlayer
    );
  }

}( window, function factory( getSize, Outlayer ) {
  'use strict';

  // layout mode class
  function LayoutMode( isotope ) {
    this.isotope = isotope;
    // link properties
    if ( isotope ) {
      this.options = isotope.options[ this.namespace ];
      this.element = isotope.element;
      this.items = isotope.filteredItems;
      this.size = isotope.size;
    }
  }

  var proto = LayoutMode.prototype;

  /**
   * some methods should just defer to default Outlayer method
   * and reference the Isotope instance as `this`
   **/
  var facadeMethods = [
    '_resetLayout',
    '_getItemLayoutPosition',
    '_manageStamp',
    '_getContainerSize',
    '_getElementOffset',
    'needsResizeLayout',
    '_getOption'
  ];

  facadeMethods.forEach( function( methodName ) {
    proto[ methodName ] = function() {
      return Outlayer.prototype[ methodName ].apply( this.isotope, arguments );
    };
  });

  // -----  ----- //

  // for horizontal layout modes, check vertical size
  proto.needsVerticalResizeLayout = function() {
    // don't trigger if size did not change
    var size = getSize( this.isotope.element );
    // check that this.size and size are there
    // IE8 triggers resize on body size change, so they might not be
    var hasSizes = this.isotope.size && size;
    return hasSizes && size.innerHeight != this.isotope.size.innerHeight;
  };

  // ----- measurements ----- //

  proto._getMeasurement = function() {
    this.isotope._getMeasurement.apply( this, arguments );
  };

  proto.getColumnWidth = function() {
    this.getSegmentSize( 'column', 'Width' );
  };

  proto.getRowHeight = function() {
    this.getSegmentSize( 'row', 'Height' );
  };

  /**
   * get columnWidth or rowHeight
   * segment: 'column' or 'row'
   * size 'Width' or 'Height'
   **/
  proto.getSegmentSize = function( segment, size ) {
    var segmentName = segment + size;
    var outerSize = 'outer' + size;
    // columnWidth / outerWidth // rowHeight / outerHeight
    this._getMeasurement( segmentName, outerSize );
    // got rowHeight or columnWidth, we can chill
    if ( this[ segmentName ] ) {
      return;
    }
    // fall back to item of first element
    var firstItemSize = this.getFirstItemSize();
    this[ segmentName ] = firstItemSize && firstItemSize[ outerSize ] ||
        // or size of container
        this.isotope.size[ 'inner' + size ];
  };

  proto.getFirstItemSize = function() {
    var firstItem = this.isotope.filteredItems[0];
    return firstItem && firstItem.element && getSize( firstItem.element );
  };

  // ----- methods that should reference isotope ----- //

  proto.layout = function() {
    this.isotope.layout.apply( this.isotope, arguments );
  };

  proto.getSize = function() {
    this.isotope.getSize();
    this.size = this.isotope.size;
  };

  // -------------------------- create -------------------------- //

  LayoutMode.modes = {};

  LayoutMode.create = function( namespace, options ) {

    function Mode() {
      LayoutMode.apply( this, arguments );
    }

    Mode.prototype = Object.create( proto );
    Mode.prototype.constructor = Mode;

    // default options
    if ( options ) {
      Mode.options = options;
    }

    Mode.prototype.namespace = namespace;
    // register in Isotope
    LayoutMode.modes[ namespace ] = Mode;

    return Mode;
  };

  return LayoutMode;

}));

/*!
 * Masonry v4.2.1
 * Cascading grid layout library
 * https://masonry.desandro.com
 * MIT License
 * by David DeSandro
 */

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /*globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'masonry-layout/masonry',[
          'outlayer/outlayer',
          'get-size/get-size'
        ],
        factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        require('outlayer'),
        require('get-size')
    );
  } else {
    // browser global
    window.Masonry = factory(
        window.Outlayer,
        window.getSize
    );
  }

}( window, function factory( Outlayer, getSize ) {



// -------------------------- masonryDefinition -------------------------- //

  // create an Outlayer layout class
  var Masonry = Outlayer.create('masonry');
  // isFitWidth -> fitWidth
  Masonry.compatOptions.fitWidth = 'isFitWidth';

  var proto = Masonry.prototype;

  proto._resetLayout = function() {
    this.getSize();
    this._getMeasurement( 'columnWidth', 'outerWidth' );
    this._getMeasurement( 'gutter', 'outerWidth' );
    this.measureColumns();

    // reset column Y
    this.colYs = [];
    for ( var i=0; i < this.cols; i++ ) {
      this.colYs.push( 0 );
    }

    this.maxY = 0;
    this.horizontalColIndex = 0;
  };

  proto.measureColumns = function() {
    this.getContainerWidth();
    // if columnWidth is 0, default to outerWidth of first item
    if ( !this.columnWidth ) {
      var firstItem = this.items[0];
      var firstItemElem = firstItem && firstItem.element;
      // columnWidth fall back to item of first element
      this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth ||
          // if first elem has no width, default to size of container
          this.containerWidth;
    }

    var columnWidth = this.columnWidth += this.gutter;

    // calculate columns
    var containerWidth = this.containerWidth + this.gutter;
    var cols = containerWidth / columnWidth;
    // fix rounding errors, typically with gutters
    var excess = columnWidth - containerWidth % columnWidth;
    // if overshoot is less than a pixel, round up, otherwise floor it
    var mathMethod = excess && excess < 1 ? 'round' : 'floor';
    cols = Math[ mathMethod ]( cols );
    this.cols = Math.max( cols, 1 );
  };

  proto.getContainerWidth = function() {
    // container is parent if fit width
    var isFitWidth = this._getOption('fitWidth');
    var container = isFitWidth ? this.element.parentNode : this.element;
    // check that this.size and size are there
    // IE8 triggers resize on body size change, so they might not be
    var size = getSize( container );
    this.containerWidth = size && size.innerWidth;
  };

  proto._getItemLayoutPosition = function( item ) {
    item.getSize();
    // how many columns does this brick span
    var remainder = item.size.outerWidth % this.columnWidth;
    var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
    // round if off by 1 pixel, otherwise use ceil
    var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth );
    colSpan = Math.min( colSpan, this.cols );
    // use horizontal or top column position
    var colPosMethod = this.options.horizontalOrder ?
        '_getHorizontalColPosition' : '_getTopColPosition';
    var colPosition = this[ colPosMethod ]( colSpan, item );
    // position the brick
    var position = {
      x: this.columnWidth * colPosition.col,
      y: colPosition.y
    };
    // apply setHeight to necessary columns
    var setHeight = colPosition.y + item.size.outerHeight;
    var setMax = colSpan + colPosition.col;
    for ( var i = colPosition.col; i < setMax; i++ ) {
      this.colYs[i] = setHeight;
    }

    return position;
  };

  proto._getTopColPosition = function( colSpan ) {
    var colGroup = this._getTopColGroup( colSpan );
    // get the minimum Y value from the columns
    var minimumY = Math.min.apply( Math, colGroup );

    return {
      col: colGroup.indexOf( minimumY ),
      y: minimumY,
    };
  };

  /**
   * @param {Number} colSpan - number of columns the element spans
   * @returns {Array} colGroup
   */
  proto._getTopColGroup = function( colSpan ) {
    if ( colSpan < 2 ) {
      // if brick spans only one column, use all the column Ys
      return this.colYs;
    }

    var colGroup = [];
    // how many different places could this brick fit horizontally
    var groupCount = this.cols + 1 - colSpan;
    // for each group potential horizontal position
    for ( var i = 0; i < groupCount; i++ ) {
      colGroup[i] = this._getColGroupY( i, colSpan );
    }
    return colGroup;
  };

  proto._getColGroupY = function( col, colSpan ) {
    if ( colSpan < 2 ) {
      return this.colYs[ col ];
    }
    // make an array of colY values for that one group
    var groupColYs = this.colYs.slice( col, col + colSpan );
    // and get the max value of the array
    return Math.max.apply( Math, groupColYs );
  };

  // get column position based on horizontal index. #873
  proto._getHorizontalColPosition = function( colSpan, item ) {
    var col = this.horizontalColIndex % this.cols;
    var isOver = colSpan > 1 && col + colSpan > this.cols;
    // shift to next row if item can't fit on current row
    col = isOver ? 0 : col;
    // don't let zero-size items take up space
    var hasSize = item.size.outerWidth && item.size.outerHeight;
    this.horizontalColIndex = hasSize ? col + colSpan : this.horizontalColIndex;

    return {
      col: col,
      y: this._getColGroupY( col, colSpan ),
    };
  };

  proto._manageStamp = function( stamp ) {
    var stampSize = getSize( stamp );
    var offset = this._getElementOffset( stamp );
    // get the columns that this stamp affects
    var isOriginLeft = this._getOption('originLeft');
    var firstX = isOriginLeft ? offset.left : offset.right;
    var lastX = firstX + stampSize.outerWidth;
    var firstCol = Math.floor( firstX / this.columnWidth );
    firstCol = Math.max( 0, firstCol );
    var lastCol = Math.floor( lastX / this.columnWidth );
    // lastCol should not go over if multiple of columnWidth #425
    lastCol -= lastX % this.columnWidth ? 0 : 1;
    lastCol = Math.min( this.cols - 1, lastCol );
    // set colYs to bottom of the stamp

    var isOriginTop = this._getOption('originTop');
    var stampMaxY = ( isOriginTop ? offset.top : offset.bottom ) +
        stampSize.outerHeight;
    for ( var i = firstCol; i <= lastCol; i++ ) {
      this.colYs[i] = Math.max( stampMaxY, this.colYs[i] );
    }
  };

  proto._getContainerSize = function() {
    this.maxY = Math.max.apply( Math, this.colYs );
    var size = {
      height: this.maxY
    };

    if ( this._getOption('fitWidth') ) {
      size.width = this._getContainerFitWidth();
    }

    return size;
  };

  proto._getContainerFitWidth = function() {
    var unusedCols = 0;
    // count unused columns
    var i = this.cols;
    while ( --i ) {
      if ( this.colYs[i] !== 0 ) {
        break;
      }
      unusedCols++;
    }
    // fit container to columns that have been used
    return ( this.cols - unusedCols ) * this.columnWidth - this.gutter;
  };

  proto.needsResizeLayout = function() {
    var previousWidth = this.containerWidth;
    this.getContainerWidth();
    return previousWidth != this.containerWidth;
  };

  return Masonry;

}));

/*!
 * Masonry layout mode
 * sub-classes Masonry
 * https://masonry.desandro.com
 */

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /*globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'isotope-layout/js/layout-modes/masonry',[
          '../layout-mode',
          'masonry-layout/masonry'
        ],
        factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        require('../layout-mode'),
        require('masonry-layout')
    );
  } else {
    // browser global
    factory(
        window.Isotope.LayoutMode,
        window.Masonry
    );
  }

}( window, function factory( LayoutMode, Masonry ) {
  'use strict';

// -------------------------- masonryDefinition -------------------------- //

  // create an Outlayer layout class
  var MasonryMode = LayoutMode.create('masonry');

  var proto = MasonryMode.prototype;

  var keepModeMethods = {
    _getElementOffset: true,
    layout: true,
    _getMeasurement: true
  };

  // inherit Masonry prototype
  for ( var method in Masonry.prototype ) {
    // do not inherit mode methods
    if ( !keepModeMethods[ method ] ) {
      proto[ method ] = Masonry.prototype[ method ];
    }
  }

  var measureColumns = proto.measureColumns;
  proto.measureColumns = function() {
    // set items, used if measuring first item
    this.items = this.isotope.filteredItems;
    measureColumns.call( this );
  };

  // point to mode options for fitWidth
  var _getOption = proto._getOption;
  proto._getOption = function( option ) {
    if ( option == 'fitWidth' ) {
      return this.options.isFitWidth !== undefined ?
          this.options.isFitWidth : this.options.fitWidth;
    }
    return _getOption.apply( this.isotope, arguments );
  };

  return MasonryMode;

}));

/**
 * fitRows layout mode
 */

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /*globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'isotope-layout/js/layout-modes/fit-rows',[
          '../layout-mode'
        ],
        factory );
  } else if ( typeof exports == 'object' ) {
    // CommonJS
    module.exports = factory(
        require('../layout-mode')
    );
  } else {
    // browser global
    factory(
        window.Isotope.LayoutMode
    );
  }

}( window, function factory( LayoutMode ) {
  'use strict';

  var FitRows = LayoutMode.create('fitRows');

  var proto = FitRows.prototype;

  proto._resetLayout = function() {
    this.x = 0;
    this.y = 0;
    this.maxY = 0;
    this._getMeasurement( 'gutter', 'outerWidth' );
  };

  proto._getItemLayoutPosition = function( item ) {
    item.getSize();

    var itemWidth = item.size.outerWidth + this.gutter;
    // if this element cannot fit in the current row
    var containerWidth = this.isotope.size.innerWidth + this.gutter;
    if ( this.x !== 0 && itemWidth + this.x > containerWidth ) {
      this.x = 0;
      this.y = this.maxY;
    }

    var position = {
      x: this.x,
      y: this.y
    };

    this.maxY = Math.max( this.maxY, this.y + item.size.outerHeight );
    this.x += itemWidth;

    return position;
  };

  proto._getContainerSize = function() {
    return { height: this.maxY };
  };

  return FitRows;

}));

/**
 * vertical layout mode
 */

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /*globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( 'isotope-layout/js/layout-modes/vertical',[
          '../layout-mode'
        ],
        factory );
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        require('../layout-mode')
    );
  } else {
    // browser global
    factory(
        window.Isotope.LayoutMode
    );
  }

}( window, function factory( LayoutMode ) {
  'use strict';

  var Vertical = LayoutMode.create( 'vertical', {
    horizontalAlignment: 0
  });

  var proto = Vertical.prototype;

  proto._resetLayout = function() {
    this.y = 0;
  };

  proto._getItemLayoutPosition = function( item ) {
    item.getSize();
    var x = ( this.isotope.size.innerWidth - item.size.outerWidth ) *
        this.options.horizontalAlignment;
    var y = this.y;
    this.y += item.size.outerHeight;
    return { x: x, y: y };
  };

  proto._getContainerSize = function() {
    return { height: this.y };
  };

  return Vertical;

}));

/*!
 * Isotope v3.0.6
 *
 * Licensed GPLv3 for open source use
 * or Isotope Commercial License for commercial use
 *
 * https://isotope.metafizzy.co
 * Copyright 2010-2018 Metafizzy
 */

( function( window, factory ) {
  // universal module definition
  /* jshint strict: false */ /*globals define, module, require */
  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( [
          'outlayer/outlayer',
          'get-size/get-size',
          'desandro-matches-selector/matches-selector',
          'fizzy-ui-utils/utils',
          'isotope-layout/js/item',
          'isotope-layout/js/layout-mode',
          // include default layout modes
          'isotope-layout/js/layout-modes/masonry',
          'isotope-layout/js/layout-modes/fit-rows',
          'isotope-layout/js/layout-modes/vertical'
        ],
        function( Outlayer, getSize, matchesSelector, utils, Item, LayoutMode ) {
          return factory( window, Outlayer, getSize, matchesSelector, utils, Item, LayoutMode );
        });
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('outlayer'),
        require('get-size'),
        require('desandro-matches-selector'),
        require('fizzy-ui-utils'),
        require('isotope-layout/js/item'),
        require('isotope-layout/js/layout-mode'),
        // include default layout modes
        require('isotope-layout/js/layout-modes/masonry'),
        require('isotope-layout/js/layout-modes/fit-rows'),
        require('isotope-layout/js/layout-modes/vertical')
    );
  } else {
    // browser global
    window.Isotope = factory(
        window,
        window.Outlayer,
        window.getSize,
        window.matchesSelector,
        window.fizzyUIUtils,
        window.Isotope.Item,
        window.Isotope.LayoutMode
    );
  }

}( window, function factory( window, Outlayer, getSize, matchesSelector, utils,
                             Item, LayoutMode ) {



// -------------------------- vars -------------------------- //

  var jQuery = window.jQuery;

// -------------------------- helpers -------------------------- //

  var trim = String.prototype.trim ?
      function( str ) {
        return str.trim();
      } :
      function( str ) {
        return str.replace( /^\s+|\s+$/g, '' );
      };

// -------------------------- isotopeDefinition -------------------------- //

  // create an Outlayer layout class
  var Isotope = Outlayer.create( 'isotope', {
    layoutMode: 'masonry',
    isJQueryFiltering: true,
    sortAscending: true
  });

  Isotope.Item = Item;
  Isotope.LayoutMode = LayoutMode;

  var proto = Isotope.prototype;

  proto._create = function() {
    this.itemGUID = 0;
    // functions that sort items
    this._sorters = {};
    this._getSorters();
    // call super
    Outlayer.prototype._create.call( this );

    // create layout modes
    this.modes = {};
    // start filteredItems with all items
    this.filteredItems = this.items;
    // keep of track of sortBys
    this.sortHistory = [ 'original-order' ];
    // create from registered layout modes
    for ( var name in LayoutMode.modes ) {
      this._initLayoutMode( name );
    }
  };

  proto.reloadItems = function() {
    // reset item ID counter
    this.itemGUID = 0;
    // call super
    Outlayer.prototype.reloadItems.call( this );
  };

  proto._itemize = function() {
    var items = Outlayer.prototype._itemize.apply( this, arguments );
    // assign ID for original-order
    for ( var i=0; i < items.length; i++ ) {
      var item = items[i];
      item.id = this.itemGUID++;
    }
    this._updateItemsSortData( items );
    return items;
  };


  // -------------------------- layout -------------------------- //

  proto._initLayoutMode = function( name ) {
    var Mode = LayoutMode.modes[ name ];
    // set mode options
    // HACK extend initial options, back-fill in default options
    var initialOpts = this.options[ name ] || {};
    this.options[ name ] = Mode.options ?
        utils.extend( Mode.options, initialOpts ) : initialOpts;
    // init layout mode instance
    this.modes[ name ] = new Mode( this );
  };


  proto.layout = function() {
    // if first time doing layout, do all magic
    if ( !this._isLayoutInited && this._getOption('initLayout') ) {
      this.arrange();
      return;
    }
    this._layout();
  };

  // private method to be used in layout() & magic()
  proto._layout = function() {
    // don't animate first layout
    var isInstant = this._getIsInstant();
    // layout flow
    this._resetLayout();
    this._manageStamps();
    this.layoutItems( this.filteredItems, isInstant );

    // flag for initalized
    this._isLayoutInited = true;
  };

  // filter + sort + layout
  proto.arrange = function( opts ) {
    // set any options pass
    this.option( opts );
    this._getIsInstant();
    // filter, sort, and layout

    // filter
    var filtered = this._filter( this.items );
    this.filteredItems = filtered.matches;

    this._bindArrangeComplete();

    if ( this._isInstant ) {
      this._noTransition( this._hideReveal, [ filtered ] );
    } else {
      this._hideReveal( filtered );
    }

    this._sort();
    this._layout();
  };
  // alias to _init for main plugin method
  proto._init = proto.arrange;

  proto._hideReveal = function( filtered ) {
    this.reveal( filtered.needReveal );
    this.hide( filtered.needHide );
  };

  // HACK
  // Don't animate/transition first layout
  // Or don't animate/transition other layouts
  proto._getIsInstant = function() {
    var isLayoutInstant = this._getOption('layoutInstant');
    var isInstant = isLayoutInstant !== undefined ? isLayoutInstant :
        !this._isLayoutInited;
    this._isInstant = isInstant;
    return isInstant;
  };

  // listen for layoutComplete, hideComplete and revealComplete
  // to trigger arrangeComplete
  proto._bindArrangeComplete = function() {
    // listen for 3 events to trigger arrangeComplete
    var isLayoutComplete, isHideComplete, isRevealComplete;
    var _this = this;
    function arrangeParallelCallback() {
      if ( isLayoutComplete && isHideComplete && isRevealComplete ) {
        _this.dispatchEvent( 'arrangeComplete', null, [ _this.filteredItems ] );
      }
    }
    this.once( 'layoutComplete', function() {
      isLayoutComplete = true;
      arrangeParallelCallback();
    });
    this.once( 'hideComplete', function() {
      isHideComplete = true;
      arrangeParallelCallback();
    });
    this.once( 'revealComplete', function() {
      isRevealComplete = true;
      arrangeParallelCallback();
    });
  };

  // -------------------------- filter -------------------------- //

  proto._filter = function( items ) {
    var filter = this.options.filter;
    filter = filter || '*';
    var matches = [];
    var hiddenMatched = [];
    var visibleUnmatched = [];

    var test = this._getFilterTest( filter );

    // test each item
    for ( var i=0; i < items.length; i++ ) {
      var item = items[i];
      if ( item.isIgnored ) {
        continue;
      }
      // add item to either matched or unmatched group
      var isMatched = test( item );
      // item.isFilterMatched = isMatched;
      // add to matches if its a match
      if ( isMatched ) {
        matches.push( item );
      }
      // add to additional group if item needs to be hidden or revealed
      if ( isMatched && item.isHidden ) {
        hiddenMatched.push( item );
      } else if ( !isMatched && !item.isHidden ) {
        visibleUnmatched.push( item );
      }
    }

    // return collections of items to be manipulated
    return {
      matches: matches,
      needReveal: hiddenMatched,
      needHide: visibleUnmatched
    };
  };

  // get a jQuery, function, or a matchesSelector test given the filter
  proto._getFilterTest = function( filter ) {
    if ( jQuery && this.options.isJQueryFiltering ) {
      // use jQuery
      return function( item ) {
        return jQuery( item.element ).is( filter );
      };
    }
    if ( typeof filter == 'function' ) {
      // use filter as function
      return function( item ) {
        return filter( item.element );
      };
    }
    // default, use filter as selector string
    return function( item ) {
      return matchesSelector( item.element, filter );
    };
  };

  // -------------------------- sorting -------------------------- //

  /**
   * @params {Array} elems
   * @public
   */
  proto.updateSortData = function( elems ) {
    // get items
    var items;
    if ( elems ) {
      elems = utils.makeArray( elems );
      items = this.getItems( elems );
    } else {
      // update all items if no elems provided
      items = this.items;
    }

    this._getSorters();
    this._updateItemsSortData( items );
  };

  proto._getSorters = function() {
    var getSortData = this.options.getSortData;
    for ( var key in getSortData ) {
      var sorter = getSortData[ key ];
      this._sorters[ key ] = mungeSorter( sorter );
    }
  };

  /**
   * @params {Array} items - of Isotope.Items
   * @private
   */
  proto._updateItemsSortData = function( items ) {
    // do not update if no items
    var len = items && items.length;

    for ( var i=0; len && i < len; i++ ) {
      var item = items[i];
      item.updateSortData();
    }
  };

  // ----- munge sorter ----- //

  // encapsulate this, as we just need mungeSorter
  // other functions in here are just for munging
  var mungeSorter = ( function() {
    // add a magic layer to sorters for convienent shorthands
    // `.foo-bar` will use the text of .foo-bar querySelector
    // `[foo-bar]` will use attribute
    // you can also add parser
    // `.foo-bar parseInt` will parse that as a number
    function mungeSorter( sorter ) {
      // if not a string, return function or whatever it is
      if ( typeof sorter != 'string' ) {
        return sorter;
      }
      // parse the sorter string
      var args = trim( sorter ).split(' ');
      var query = args[0];
      // check if query looks like [an-attribute]
      var attrMatch = query.match( /^\[(.+)\]$/ );
      var attr = attrMatch && attrMatch[1];
      var getValue = getValueGetter( attr, query );
      // use second argument as a parser
      var parser = Isotope.sortDataParsers[ args[1] ];
      // parse the value, if there was a parser
      sorter = parser ? function( elem ) {
            return elem && parser( getValue( elem ) );
          } :
          // otherwise just return value
          function( elem ) {
            return elem && getValue( elem );
          };

      return sorter;
    }

    // get an attribute getter, or get text of the querySelector
    function getValueGetter( attr, query ) {
      // if query looks like [foo-bar], get attribute
      if ( attr ) {
        return function getAttribute( elem ) {
          return elem.getAttribute( attr );
        };
      }

      // otherwise, assume its a querySelector, and get its text
      return function getChildText( elem ) {
        var child = elem.querySelector( query );
        return child && child.textContent;
      };
    }

    return mungeSorter;
  })();

  // parsers used in getSortData shortcut strings
  Isotope.sortDataParsers = {
    'parseInt': function( val ) {
      return parseInt( val, 10 );
    },
    'parseFloat': function( val ) {
      return parseFloat( val );
    }
  };

  // ----- sort method ----- //

  // sort filteredItem order
  proto._sort = function() {
    if ( !this.options.sortBy ) {
      return;
    }
    // keep track of sortBy History
    var sortBys = utils.makeArray( this.options.sortBy );
    if ( !this._getIsSameSortBy( sortBys ) ) {
      // concat all sortBy and sortHistory, add to front, oldest goes in last
      this.sortHistory = sortBys.concat( this.sortHistory );
    }
    // sort magic
    var itemSorter = getItemSorter( this.sortHistory, this.options.sortAscending );
    this.filteredItems.sort( itemSorter );
  };

  // check if sortBys is same as start of sortHistory
  proto._getIsSameSortBy = function( sortBys ) {
    for ( var i=0; i < sortBys.length; i++ ) {
      if ( sortBys[i] != this.sortHistory[i] ) {
        return false;
      }
    }
    return true;
  };

  // returns a function used for sorting
  function getItemSorter( sortBys, sortAsc ) {
    return function sorter( itemA, itemB ) {
      // cycle through all sortKeys
      for ( var i = 0; i < sortBys.length; i++ ) {
        var sortBy = sortBys[i];
        var a = itemA.sortData[ sortBy ];
        var b = itemB.sortData[ sortBy ];
        if ( a > b || a < b ) {
          // if sortAsc is an object, use the value given the sortBy key
          var isAscending = sortAsc[ sortBy ] !== undefined ? sortAsc[ sortBy ] : sortAsc;
          var direction = isAscending ? 1 : -1;
          return ( a > b ? 1 : -1 ) * direction;
        }
      }
      return 0;
    };
  }

  // -------------------------- methods -------------------------- //

  // get layout mode
  proto._mode = function() {
    var layoutMode = this.options.layoutMode;
    var mode = this.modes[ layoutMode ];
    if ( !mode ) {
      // TODO console.error
      throw new Error( 'No layout mode: ' + layoutMode );
    }
    // HACK sync mode's options
    // any options set after init for layout mode need to be synced
    mode.options = this.options[ layoutMode ];
    return mode;
  };

  proto._resetLayout = function() {
    // trigger original reset layout
    Outlayer.prototype._resetLayout.call( this );
    this._mode()._resetLayout();
  };

  proto._getItemLayoutPosition = function( item  ) {
    return this._mode()._getItemLayoutPosition( item );
  };

  proto._manageStamp = function( stamp ) {
    this._mode()._manageStamp( stamp );
  };

  proto._getContainerSize = function() {
    return this._mode()._getContainerSize();
  };

  proto.needsResizeLayout = function() {
    return this._mode().needsResizeLayout();
  };

  // -------------------------- adding & removing -------------------------- //

  // HEADS UP overwrites default Outlayer appended
  proto.appended = function( elems ) {
    var items = this.addItems( elems );
    if ( !items.length ) {
      return;
    }
    // filter, layout, reveal new items
    var filteredItems = this._filterRevealAdded( items );
    // add to filteredItems
    this.filteredItems = this.filteredItems.concat( filteredItems );
  };

  // HEADS UP overwrites default Outlayer prepended
  proto.prepended = function( elems ) {
    var items = this._itemize( elems );
    if ( !items.length ) {
      return;
    }
    // start new layout
    this._resetLayout();
    this._manageStamps();
    // filter, layout, reveal new items
    var filteredItems = this._filterRevealAdded( items );
    // layout previous items
    this.layoutItems( this.filteredItems );
    // add to items and filteredItems
    this.filteredItems = filteredItems.concat( this.filteredItems );
    this.items = items.concat( this.items );
  };

  proto._filterRevealAdded = function( items ) {
    var filtered = this._filter( items );
    this.hide( filtered.needHide );
    // reveal all new items
    this.reveal( filtered.matches );
    // layout new items, no transition
    this.layoutItems( filtered.matches, true );
    return filtered.matches;
  };

  /**
   * Filter, sort, and layout newly-appended item elements
   * @param {Array or NodeList or Element} elems
   */
  proto.insert = function( elems ) {
    var items = this.addItems( elems );
    if ( !items.length ) {
      return;
    }
    // append item elements
    var i, item;
    var len = items.length;
    for ( i=0; i < len; i++ ) {
      item = items[i];
      this.element.appendChild( item.element );
    }
    // filter new stuff
    var filteredInsertItems = this._filter( items ).matches;
    // set flag
    for ( i=0; i < len; i++ ) {
      items[i].isLayoutInstant = true;
    }
    this.arrange();
    // reset flag
    for ( i=0; i < len; i++ ) {
      delete items[i].isLayoutInstant;
    }
    this.reveal( filteredInsertItems );
  };

  var _remove = proto.remove;
  proto.remove = function( elems ) {
    elems = utils.makeArray( elems );
    var removeItems = this.getItems( elems );
    // do regular thing
    _remove.call( this, elems );
    // bail if no items to remove
    var len = removeItems && removeItems.length;
    // remove elems from filteredItems
    for ( var i=0; len && i < len; i++ ) {
      var item = removeItems[i];
      // remove item from collection
      utils.removeFrom( this.filteredItems, item );
    }
  };

  proto.shuffle = function() {
    // update random sortData
    for ( var i=0; i < this.items.length; i++ ) {
      var item = this.items[i];
      item.sortData.random = Math.random();
    }
    this.options.sortBy = 'random';
    this._sort();
    this._layout();
  };

  /**
   * trigger fn without transition
   * kind of hacky to have this in the first place
   * @param {Function} fn
   * @param {Array} args
   * @returns ret
   * @private
   */
  proto._noTransition = function( fn, args ) {
    // save transitionDuration before disabling
    var transitionDuration = this.options.transitionDuration;
    // disable transition
    this.options.transitionDuration = 0;
    // do it
    var returnValue = fn.apply( this, args );
    // re-enable transition for reveal
    this.options.transitionDuration = transitionDuration;
    return returnValue;
  };

  // ----- helper methods ----- //

  /**
   * getter method for getting filtered item elements
   * @returns {Array} elems - collection of item elements
   */
  proto.getFilteredItemElements = function() {
    return this.filteredItems.map( function( item ) {
      return item.element;
    });
  };

  // -----  ----- //

  return Isotope;

}));

/**
 * IMPORTANT WOODMART NOTICE: this file is a modified version of the original isotope.pkgd.js file.
 * If you update this file, ensure to re-apply fixes (see: https://github.com/metafizzy/flickity/pull/900).
 */

/*!
 * Packery v3.0.0
 * Gapless, draggable grid layouts
 * MIT License
 * https://packery.metafizzy.co
 * Copyright 2013-2025 Metafizzy
 */

/**
 * Bridget makes jQuery widgets
 * v2.0.1
 * MIT license
 */

/* jshint browser: true, strict: true, undef: true, unused: true */

( function( window, factory ) {
	// universal module definition
	/*jshint strict: false */ /* globals define, module, require */
	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) {
			return factory( window, jQuery );
		});
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory(
			window,
			require('jquery')
		);
	} else {
		// browser global
		window.jQueryBridget = factory(
			window,
			window.jQuery
		);
	}

}( window, function factory( window, jQuery ) {
	'use strict';

// ----- utils ----- //

	var arraySlice = Array.prototype.slice;

// helper function for logging errors
// $.error breaks jQuery chaining
	var console = window.console;
	var logError = typeof console == 'undefined' ? function() {} :
		function( message ) {
			console.error( message );
		};

// ----- jQueryBridget ----- //

	function jQueryBridget( namespace, PluginClass, $ ) {
		$ = $ || jQuery || window.jQuery;
		if ( !$ ) {
			return;
		}

		// add option method -> $().plugin('option', {...})
		if ( !PluginClass.prototype.option ) {
			// option setter
			PluginClass.prototype.option = function( opts ) {
				// bail out if not an object
				if ( !$.isPlainObject( opts ) ){
					return;
				}
				this.options = $.extend( true, this.options, opts );
			};
		}

		// make jQuery plugin
		$.fn[ namespace ] = function( arg0 /*, arg1 */ ) {
			if ( typeof arg0 == 'string' ) {
				// method call $().plugin( 'methodName', { options } )
				// shift arguments by 1
				var args = arraySlice.call( arguments, 1 );
				return methodCall( this, arg0, args );
			}
			// just $().plugin({ options })
			plainCall( this, arg0 );
			return this;
		};

		// $().plugin('methodName')
		function methodCall( $elems, methodName, args ) {
			var returnValue;
			var pluginMethodStr = '$().' + namespace + '("' + methodName + '")';

			$elems.each( function( i, elem ) {
				// get instance
				var instance = $.data( elem, namespace );
				if ( !instance ) {
					logError( namespace + ' not initialized. Cannot call methods, i.e. ' +
						pluginMethodStr );
					return;
				}

				var method = instance[ methodName ];
				if ( !method || methodName.charAt(0) == '_' ) {
					logError( pluginMethodStr + ' is not a valid method' );
					return;
				}

				// apply method, get return value
				var value = method.apply( instance, args );
				// set return value if value is returned, use only first value
				returnValue = returnValue === undefined ? value : returnValue;
			});

			return returnValue !== undefined ? returnValue : $elems;
		}

		function plainCall( $elems, options ) {
			$elems.each( function( i, elem ) {
				var instance = $.data( elem, namespace );
				if ( instance ) {
					// set options & init
					instance.option( options );
					instance._init();
				} else {
					// initialize new instance
					instance = new PluginClass( elem, options );
					$.data( elem, namespace, instance );
				}
			});
		}

		updateJQuery( $ );

	}

// ----- updateJQuery ----- //

// set $.bridget for v1 backwards compatibility
	function updateJQuery( $ ) {
		if ( !$ || ( $ && $.bridget ) ) {
			return;
		}
		$.bridget = jQueryBridget;
	}

	updateJQuery( jQuery || window.jQuery );

// -----  ----- //

	return jQueryBridget;

}));

/*!
 * getSize v2.0.3
 * measure size of elements
 * MIT license
 */

/* jshint browser: true, strict: true, undef: true, unused: true */
/* globals console: false */

( function( window, factory ) {
	/* jshint strict: false */ /* globals define, module */
	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( 'get-size/get-size',factory );
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory();
	} else {
		// browser global
		window.getSize = factory();
	}

})( window, function factory() {
	'use strict';

// -------------------------- helpers -------------------------- //

// get a number from a string, not a percentage
	function getStyleSize( value ) {
		var num = parseFloat( value );
		// not a percent like '100%', and a number
		var isValid = value.indexOf('%') == -1 && !isNaN( num );
		return isValid && num;
	}

	function noop() {}

	var logError = typeof console == 'undefined' ? noop :
		function( message ) {
			console.error( message );
		};

// -------------------------- measurements -------------------------- //

	var measurements = [
		'paddingLeft',
		'paddingRight',
		'paddingTop',
		'paddingBottom',
		'marginLeft',
		'marginRight',
		'marginTop',
		'marginBottom',
		'borderLeftWidth',
		'borderRightWidth',
		'borderTopWidth',
		'borderBottomWidth'
	];

	var measurementsLength = measurements.length;

	function getZeroSize() {
		var size = {
			width: 0,
			height: 0,
			innerWidth: 0,
			innerHeight: 0,
			outerWidth: 0,
			outerHeight: 0
		};
		for ( var i=0; i < measurementsLength; i++ ) {
			var measurement = measurements[i];
			size[ measurement ] = 0;
		}
		return size;
	}

// -------------------------- getStyle -------------------------- //

	/**
	 * getStyle, get style of element, check for Firefox bug
	 * https://bugzilla.mozilla.org/show_bug.cgi?id=548397
	 */
	function getStyle( elem ) {
		var style = getComputedStyle( elem );
		if ( !style ) {
			logError( 'Style returned ' + style +
				'. Are you running this code in a hidden iframe on Firefox? ' +
				'See https://bit.ly/getsizebug1' );
		}
		return style;
	}

// -------------------------- setup -------------------------- //

	var isSetup = false;

	var isBoxSizeOuter;

	/**
	 * setup
	 * check isBoxSizerOuter
	 * do on first getSize() rather than on page load for Firefox bug
	 */
	function setup() {
		// setup once
		if ( isSetup ) {
			return;
		}
		isSetup = true;

		// -------------------------- box sizing -------------------------- //

		/**
		 * Chrome & Safari measure the outer-width on style.width on border-box elems
		 * IE11 & Firefox<29 measures the inner-width
		 */
		var div = document.createElement('div');
		div.style.width = '200px';
		div.style.padding = '1px 2px 3px 4px';
		div.style.borderStyle = 'solid';
		div.style.borderWidth = '1px 2px 3px 4px';
		div.style.boxSizing = 'border-box';

		var body = document.body || document.documentElement;
		body.appendChild( div );
		var style = getStyle( div );
		// round value for browser zoom. desandro/masonry#928
		isBoxSizeOuter = Math.round( getStyleSize( style.width ) ) == 200;
		getSize.isBoxSizeOuter = isBoxSizeOuter;

		body.removeChild( div );
	}

// -------------------------- getSize -------------------------- //

	function getSize( elem ) {
		setup();

		// use querySeletor if elem is string
		if ( typeof elem == 'string' ) {
			elem = document.querySelector( elem );
		}

		// do not proceed on non-objects
		if ( !elem || typeof elem != 'object' || !elem.nodeType ) {
			return;
		}

		var style = getStyle( elem );

		// if hidden, everything is 0
		if ( style.display == 'none' ) {
			return getZeroSize();
		}

		var size = {};
		size.width = elem.offsetWidth;
		size.height = elem.offsetHeight;

		var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box';

		// get all measurements
		for ( var i=0; i < measurementsLength; i++ ) {
			var measurement = measurements[i];
			var value = style[ measurement ];
			var num = parseFloat( value );
			// any 'auto', 'medium' value will be 0
			size[ measurement ] = !isNaN( num ) ? num : 0;
		}

		var paddingWidth = size.paddingLeft + size.paddingRight;
		var paddingHeight = size.paddingTop + size.paddingBottom;
		var marginWidth = size.marginLeft + size.marginRight;
		var marginHeight = size.marginTop + size.marginBottom;
		var borderWidth = size.borderLeftWidth + size.borderRightWidth;
		var borderHeight = size.borderTopWidth + size.borderBottomWidth;

		var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;

		// overwrite width and height if we can get it from style
		var styleWidth = getStyleSize( style.width );
		if ( styleWidth !== false ) {
			size.width = styleWidth +
				// add padding and border unless it's already including it
				( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth );
		}

		var styleHeight = getStyleSize( style.height );
		if ( styleHeight !== false ) {
			size.height = styleHeight +
				// add padding and border unless it's already including it
				( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight );
		}

		size.innerWidth = size.width - ( paddingWidth + borderWidth );
		size.innerHeight = size.height - ( paddingHeight + borderHeight );

		size.outerWidth = size.width + marginWidth;
		size.outerHeight = size.height + marginHeight;

		return size;
	}

	return getSize;

});

/**
 * EvEmitter v1.1.0
 * Lil' event emitter
 * MIT License
 */

/* jshint unused: true, undef: true, strict: true */

( function( global, factory ) {
	// universal module definition
	/* jshint strict: false */ /* globals define, module, window */
	if ( typeof define == 'function' && define.amd ) {
		// AMD - RequireJS
		define( 'ev-emitter/ev-emitter',factory );
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS - Browserify, Webpack
		module.exports = factory();
	} else {
		// Browser globals
		global.EvEmitter = factory();
	}

}( typeof window != 'undefined' ? window : this, function() {



	function EvEmitter() {}

	var proto = EvEmitter.prototype;

	proto.on = function( eventName, listener ) {
		if ( !eventName || !listener ) {
			return;
		}
		// set events hash
		var events = this._events = this._events || {};
		// set listeners array
		var listeners = events[ eventName ] = events[ eventName ] || [];
		// only add once
		if ( listeners.indexOf( listener ) == -1 ) {
			listeners.push( listener );
		}

		return this;
	};

	proto.once = function( eventName, listener ) {
		if ( !eventName || !listener ) {
			return;
		}
		// add event
		this.on( eventName, listener );
		// set once flag
		// set onceEvents hash
		var onceEvents = this._onceEvents = this._onceEvents || {};
		// set onceListeners object
		var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
		// set flag
		onceListeners[ listener ] = true;

		return this;
	};

	proto.off = function( eventName, listener ) {
		var listeners = this._events && this._events[ eventName ];
		if ( !listeners || !listeners.length ) {
			return;
		}
		var index = listeners.indexOf( listener );
		if ( index != -1 ) {
			listeners.splice( index, 1 );
		}

		return this;
	};

	proto.emitEvent = function( eventName, args ) {
		var listeners = this._events && this._events[ eventName ];
		if ( !listeners || !listeners.length ) {
			return;
		}
		// copy over to avoid interference if .off() in listener
		listeners = listeners.slice(0);
		args = args || [];
		// once stuff
		var onceListeners = this._onceEvents && this._onceEvents[ eventName ];

		for ( var i=0; i < listeners.length; i++ ) {
			var listener = listeners[i]
			var isOnce = onceListeners && onceListeners[ listener ];
			if ( isOnce ) {
				// remove listener
				// remove before trigger to prevent recursion
				this.off( eventName, listener );
				// unset once flag
				delete onceListeners[ listener ];
			}
			// trigger listener
			listener.apply( this, args );
		}

		return this;
	};

	proto.allOff = function() {
		delete this._events;
		delete this._onceEvents;
	};

	return EvEmitter;

}));

/**
 * matchesSelector v2.0.2
 * matchesSelector( element, '.selector' )
 * MIT license
 */

/*jshint browser: true, strict: true, undef: true, unused: true */

( function( window, factory ) {
	/*global define: false, module: false */
	'use strict';
	// universal module definition
	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( 'desandro-matches-selector/matches-selector',factory );
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory();
	} else {
		// browser global
		window.matchesSelector = factory();
	}

}( window, function factory() {
	'use strict';

	var matchesMethod = ( function() {
		var ElemProto = window.Element.prototype;
		// check for the standard method name first
		if ( ElemProto.matches ) {
			return 'matches';
		}
		// check un-prefixed
		if ( ElemProto.matchesSelector ) {
			return 'matchesSelector';
		}
		// check vendor prefixes
		var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];

		for ( var i=0; i < prefixes.length; i++ ) {
			var prefix = prefixes[i];
			var method = prefix + 'MatchesSelector';
			if ( ElemProto[ method ] ) {
				return method;
			}
		}
	})();

	return function matchesSelector( elem, selector ) {
		return elem[ matchesMethod ]( selector );
	};

}));

/**
 * Fizzy UI utils v2.0.7
 * MIT license
 */

/*jshint browser: true, undef: true, unused: true, strict: true */

( function( window, factory ) {
	// universal module definition
	/*jshint strict: false */ /*globals define, module, require */

	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( 'fizzy-ui-utils/utils',[
			'desandro-matches-selector/matches-selector'
		], function( matchesSelector ) {
			return factory( window, matchesSelector );
		});
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory(
			window,
			require('desandro-matches-selector')
		);
	} else {
		// browser global
		window.fizzyUIUtils = factory(
			window,
			window.matchesSelector
		);
	}

}( window, function factory( window, matchesSelector ) {



	var utils = {};

// ----- extend ----- //

// extends objects
	utils.extend = function( a, b ) {
		for ( var prop in b ) {
			a[ prop ] = b[ prop ];
		}
		return a;
	};

// ----- modulo ----- //

	utils.modulo = function( num, div ) {
		return ( ( num % div ) + div ) % div;
	};

// ----- makeArray ----- //

	var arraySlice = Array.prototype.slice;

// turn element or nodeList into an array
	utils.makeArray = function( obj ) {
		if ( Array.isArray( obj ) ) {
			// use object if already an array
			return obj;
		}
		// return empty array if undefined or null. #6
		if ( obj === null || obj === undefined ) {
			return [];
		}

		var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
		if ( isArrayLike ) {
			// convert nodeList to array
			return arraySlice.call( obj );
		}

		// array of single index
		return [ obj ];
	};

// ----- removeFrom ----- //

	utils.removeFrom = function( ary, obj ) {
		var index = ary.indexOf( obj );
		if ( index != -1 ) {
			ary.splice( index, 1 );
		}
	};

// ----- getParent ----- //

	utils.getParent = function( elem, selector ) {
		while ( elem.parentNode && elem != document.body ) {
			elem = elem.parentNode;
			if ( matchesSelector( elem, selector ) ) {
				return elem;
			}
		}
	};

// ----- getQueryElement ----- //

// use element as selector string
	utils.getQueryElement = function( elem ) {
		if ( typeof elem == 'string' ) {
			return document.querySelector( elem );
		}
		return elem;
	};

// ----- handleEvent ----- //

// enable .ontype to trigger from .addEventListener( elem, 'type' )
	utils.handleEvent = function( event ) {
		var method = 'on' + event.type;
		if ( this[ method ] ) {
			this[ method ]( event );
		}
	};

// ----- filterFindElements ----- //

	utils.filterFindElements = function( elems, selector ) {
		// make array of elems
		elems = utils.makeArray( elems );
		var ffElems = [];
		var isElement = function (elem) {
			return (
			  typeof HTMLElement === "object" ? elem instanceof HTMLElement : elem && typeof elem === "object" && elem !== null && elem.nodeType === 1 && typeof elem.nodeName === "string"
			);
		 };

		elems.forEach( function( elem ) {
			// check that elem is an actual element
			if ( !( isElement(elem) ) ) {
				return;
			}
			// add elem if no selector
			if ( !selector ) {
				ffElems.push( elem );
				return;
			}
			// filter & find items if we have a selector
			// filter
			if ( matchesSelector( elem, selector ) ) {
				ffElems.push( elem );
			}
			// find children
			var childElems = elem.querySelectorAll( selector );
			// concat childElems to filterFound array
			for ( var i=0; i < childElems.length; i++ ) {
				ffElems.push( childElems[i] );
			}
		});

		return ffElems;
	};

// ----- debounceMethod ----- //

	utils.debounceMethod = function( _class, methodName, threshold ) {
		threshold = threshold || 100;
		// original method
		var method = _class.prototype[ methodName ];
		var timeoutName = methodName + 'Timeout';

		_class.prototype[ methodName ] = function() {
			var timeout = this[ timeoutName ];
			clearTimeout( timeout );

			var args = arguments;
			var _this = this;
			this[ timeoutName ] = setTimeout( function() {
				method.apply( _this, args );
				delete _this[ timeoutName ];
			}, threshold );
		};
	};

// ----- docReady ----- //

	utils.docReady = function( callback ) {
		var readyState = document.readyState;
		if ( readyState == 'complete' || readyState == 'interactive' ) {
			// do async to allow for other scripts to run. metafizzy/flickity#441
			setTimeout( callback );
		} else {
			document.addEventListener( 'DOMContentLoaded', callback );
		}
	};

// ----- htmlInit ----- //

// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
	utils.toDashed = function( str ) {
		return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
			return $1 + '-' + $2;
		}).toLowerCase();
	};

	var console = window.console;
	/**
	 * allow user to initialize classes via [data-namespace] or .js-namespace class
	 * htmlInit( Widget, 'widgetName' )
	 * options are parsed from data-namespace-options
	 */
	utils.htmlInit = function( WidgetClass, namespace ) {
		utils.docReady( function() {
			var dashedNamespace = utils.toDashed( namespace );
			var dataAttr = 'data-' + dashedNamespace;
			var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' );
			var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace );
			var elems = utils.makeArray( dataAttrElems )
				.concat( utils.makeArray( jsDashElems ) );
			var dataOptionsAttr = dataAttr + '-options';
			var jQuery = window.jQuery;

			elems.forEach( function( elem ) {
				var attr = elem.getAttribute( dataAttr ) ||
					elem.getAttribute( dataOptionsAttr );
				var options;
				try {
					options = attr && JSON.parse( attr );
				} catch ( error ) {
					// log error, do not initialize
					if ( console ) {
						console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className +
							': ' + error );
					}
					return;
				}
				// initialize
				var instance = new WidgetClass( elem, options );
				// make available via $().data('namespace')
				if ( jQuery ) {
					jQuery.data( elem, namespace, instance );
				}
			});

		});
	};

// -----  ----- //

	return utils;

}));

/**
 * Outlayer Item
 */

( function( window, factory ) {
	// universal module definition
	/* jshint strict: false */ /* globals define, module, require */
	if ( typeof define == 'function' && define.amd ) {
		// AMD - RequireJS
		define( 'outlayer/item',[
				'ev-emitter/ev-emitter',
				'get-size/get-size'
			],
			factory
		);
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS - Browserify, Webpack
		module.exports = factory(
			require('ev-emitter'),
			require('get-size')
		);
	} else {
		// browser global
		window.Outlayer = {};
		window.Outlayer.Item = factory(
			window.EvEmitter,
			window.getSize
		);
	}

}( window, function factory( EvEmitter, getSize ) {
	'use strict';

// ----- helpers ----- //

	function isEmptyObj( obj ) {
		for ( var prop in obj ) {
			return false;
		}
		prop = null;
		return true;
	}

// -------------------------- CSS3 support -------------------------- //


	var docElemStyle = document.documentElement.style;

	var transitionProperty = typeof docElemStyle.transition == 'string' ?
		'transition' : 'WebkitTransition';
	var transformProperty = typeof docElemStyle.transform == 'string' ?
		'transform' : 'WebkitTransform';

	var transitionEndEvent = {
		WebkitTransition: 'webkitTransitionEnd',
		transition: 'transitionend'
	}[ transitionProperty ];

// cache all vendor properties that could have vendor prefix
	var vendorProperties = {
		transform: transformProperty,
		transition: transitionProperty,
		transitionDuration: transitionProperty + 'Duration',
		transitionProperty: transitionProperty + 'Property',
		transitionDelay: transitionProperty + 'Delay'
	};

// -------------------------- Item -------------------------- //

	function Item( element, layout ) {
		if ( !element ) {
			return;
		}

		this.element = element;
		// parent layout class, i.e. Masonry, Isotope, or Packery
		this.layout = layout;
		this.position = {
			x: 0,
			y: 0
		};

		this._create();
	}

// inherit EvEmitter
	var proto = Item.prototype = Object.create( EvEmitter.prototype );
	proto.constructor = Item;

	proto._create = function() {
		// transition objects
		this._transn = {
			ingProperties: {},
			clean: {},
			onEnd: {}
		};

		this.css({
			position: 'absolute'
		});
	};

// trigger specified handler for event type
	proto.handleEvent = function( event ) {
		var method = 'on' + event.type;
		if ( this[ method ] ) {
			this[ method ]( event );
		}
	};

	proto.getSize = function() {
		this.size = getSize( this.element );
	};

	/**
	 * apply CSS styles to element
	 * @param {Object} style
	 */
	proto.css = function( style ) {
		var elemStyle = this.element.style;

		for ( var prop in style ) {
			// use vendor property if available
			var supportedProp = vendorProperties[ prop ] || prop;
			elemStyle[ supportedProp ] = style[ prop ];
		}
	};

	// measure position, and sets it
	proto.getPosition = function() {
		var style = getComputedStyle( this.element );
		var isOriginLeft = this.layout._getOption('originLeft');
		var isOriginTop = this.layout._getOption('originTop');
		var xValue = style[ isOriginLeft ? 'left' : 'right' ];
		var yValue = style[ isOriginTop ? 'top' : 'bottom' ];
		var x = parseFloat( xValue );
		var y = parseFloat( yValue );
		// convert percent to pixels
		var layoutSize = this.layout.size;
		if ( xValue.indexOf('%') != -1 ) {
			x = ( x / 100 ) * layoutSize.width;
		}
		if ( yValue.indexOf('%') != -1 ) {
			y = ( y / 100 ) * layoutSize.height;
		}
		// clean up 'auto' or other non-integer values
		x = isNaN( x ) ? 0 : x;
		y = isNaN( y ) ? 0 : y;
		// remove padding from measurement
		x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight;
		y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom;

		this.position.x = x;
		this.position.y = y;
	};

// set settled position, apply padding
	proto.layoutPosition = function() {
		var layoutSize = this.layout.size;
		var style = {};
		var isOriginLeft = this.layout._getOption('originLeft');
		var isOriginTop = this.layout._getOption('originTop');

		// x
		var xPadding = isOriginLeft ? 'paddingLeft' : 'paddingRight';
		var xProperty = isOriginLeft ? 'left' : 'right';
		var xResetProperty = isOriginLeft ? 'right' : 'left';

		var x = this.position.x + layoutSize[ xPadding ];
		// set in percentage or pixels
		style[ xProperty ] = this.getXValue( x );
		// reset other property
		style[ xResetProperty ] = '';

		// y
		var yPadding = isOriginTop ? 'paddingTop' : 'paddingBottom';
		var yProperty = isOriginTop ? 'top' : 'bottom';
		var yResetProperty = isOriginTop ? 'bottom' : 'top';

		var y = this.position.y + layoutSize[ yPadding ];
		// set in percentage or pixels
		style[ yProperty ] = this.getYValue( y );
		// reset other property
		style[ yResetProperty ] = '';

		this.css( style );
		this.emitEvent( 'layout', [ this ] );
	};

	proto.getXValue = function( x ) {
		var isHorizontal = this.layout._getOption('horizontal');
		return this.layout.options.percentPosition && !isHorizontal ?
			( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px';
	};

	proto.getYValue = function( y ) {
		var isHorizontal = this.layout._getOption('horizontal');
		return this.layout.options.percentPosition && isHorizontal ?
			( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px';
	};

	proto._transitionTo = function( x, y ) {
		this.getPosition();
		// get current x & y from top/left
		var curX = this.position.x;
		var curY = this.position.y;

		var didNotMove = x == this.position.x && y == this.position.y;

		// save end position
		this.setPosition( x, y );

		// if did not move and not transitioning, just go to layout
		if ( didNotMove && !this.isTransitioning ) {
			this.layoutPosition();
			return;
		}

		var transX = x - curX;
		var transY = y - curY;
		var transitionStyle = {};
		transitionStyle.transform = this.getTranslate( transX, transY );

		this.transition({
			to: transitionStyle,
			onTransitionEnd: {
				transform: this.layoutPosition
			},
			isCleaning: true
		});
	};

	proto.getTranslate = function( x, y ) {
		// flip cooridinates if origin on right or bottom
		var isOriginLeft = this.layout._getOption('originLeft');
		var isOriginTop = this.layout._getOption('originTop');
		x = isOriginLeft ? x : -x;
		y = isOriginTop ? y : -y;
		return 'translate3d(' + x + 'px, ' + y + 'px, 0)';
	};

// non transition + transform support
	proto.goTo = function( x, y ) {
		this.setPosition( x, y );
		this.layoutPosition();
	};

	proto.moveTo = proto._transitionTo;

	proto.setPosition = function( x, y ) {
		this.position.x = parseFloat( x );
		this.position.y = parseFloat( y );
	};

// ----- transition ----- //

	/**
	 * @param {Object} style - CSS
	 * @param {Function} onTransitionEnd
	 */

// non transition, just trigger callback
	proto._nonTransition = function( args ) {
		this.css( args.to );
		if ( args.isCleaning ) {
			this._removeStyles( args.to );
		}
		for ( var prop in args.onTransitionEnd ) {
			args.onTransitionEnd[ prop ].call( this );
		}
	};

	/**
	 * proper transition
	 * @param {Object} args - arguments
	 *   @param {Object} to - style to transition to
	 *   @param {Object} from - style to start transition from
	 *   @param {Boolean} isCleaning - removes transition styles after transition
	 *   @param {Function} onTransitionEnd - callback
	 */
	proto.transition = function( args ) {
		// redirect to nonTransition if no transition duration
		if ( !parseFloat( this.layout.options.transitionDuration ) ) {
			this._nonTransition( args );
			return;
		}

		var _transition = this._transn;
		// keep track of onTransitionEnd callback by css property
		for ( var prop in args.onTransitionEnd ) {
			_transition.onEnd[ prop ] = args.onTransitionEnd[ prop ];
		}
		// keep track of properties that are transitioning
		for ( prop in args.to ) {
			_transition.ingProperties[ prop ] = true;
			// keep track of properties to clean up when transition is done
			if ( args.isCleaning ) {
				_transition.clean[ prop ] = true;
			}
		}

		// set from styles
		if ( args.from ) {
			this.css( args.from );
			// force redraw. http://blog.alexmaccaw.com/css-transitions
			var h = this.element.offsetHeight;
			// hack for JSHint to hush about unused var
			h = null;
		}
		// enable transition
		this.enableTransition( args.to );
		// set styles that are transitioning
		this.css( args.to );

		this.isTransitioning = true;

	};

// dash before all cap letters, including first for
// WebkitTransform => -webkit-transform
	function toDashedAll( str ) {
		return str.replace( /([A-Z])/g, function( $1 ) {
			return '-' + $1.toLowerCase();
		});
	}

	var transitionProps = 'opacity,' + toDashedAll( transformProperty );

	proto.enableTransition = function(/* style */) {
		// HACK changing transitionProperty during a transition
		// will cause transition to jump
		if ( this.isTransitioning ) {
			return;
		}

		// make `transition: foo, bar, baz` from style object
		// HACK un-comment this when enableTransition can work
		// while a transition is happening
		// var transitionValues = [];
		// for ( var prop in style ) {
		//   // dash-ify camelCased properties like WebkitTransition
		//   prop = vendorProperties[ prop ] || prop;
		//   transitionValues.push( toDashedAll( prop ) );
		// }
		// munge number to millisecond, to match stagger
		var duration = this.layout.options.transitionDuration;
		duration = typeof duration == 'number' ? duration + 'ms' : duration;
		// enable transition styles
		this.css({
			transitionProperty: transitionProps,
			transitionDuration: duration,
			transitionDelay: this.staggerDelay || 0
		});
		// listen for transition end event
		this.element.addEventListener( transitionEndEvent, this, false );
	};

// ----- events ----- //

	proto.onwebkitTransitionEnd = function( event ) {
		this.ontransitionend( event );
	};

	proto.onotransitionend = function( event ) {
		this.ontransitionend( event );
	};

// properties that I munge to make my life easier
	var dashedVendorProperties = {
		'-webkit-transform': 'transform'
	};

	proto.ontransitionend = function( event ) {
		// disregard bubbled events from children
		if ( event.target !== this.element ) {
			return;
		}
		var _transition = this._transn;
		// get property name of transitioned property, convert to prefix-free
		var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName;

		// remove property that has completed transitioning
		delete _transition.ingProperties[ propertyName ];
		// check if any properties are still transitioning
		if ( isEmptyObj( _transition.ingProperties ) ) {
			// all properties have completed transitioning
			this.disableTransition();
		}
		// clean style
		if ( propertyName in _transition.clean ) {
			// clean up style
			this.element.style[ event.propertyName ] = '';
			delete _transition.clean[ propertyName ];
		}
		// trigger onTransitionEnd callback
		if ( propertyName in _transition.onEnd ) {
			var onTransitionEnd = _transition.onEnd[ propertyName ];
			onTransitionEnd.call( this );
			delete _transition.onEnd[ propertyName ];
		}

		this.emitEvent( 'transitionEnd', [ this ] );
	};

	proto.disableTransition = function() {
		this.removeTransitionStyles();
		this.element.removeEventListener( transitionEndEvent, this, false );
		this.isTransitioning = false;
	};

	/**
	 * removes style property from element
	 * @param {Object} style
	 **/
	proto._removeStyles = function( style ) {
		// clean up transition styles
		var cleanStyle = {};
		for ( var prop in style ) {
			cleanStyle[ prop ] = '';
		}
		this.css( cleanStyle );
	};

	var cleanTransitionStyle = {
		transitionProperty: '',
		transitionDuration: '',
		transitionDelay: ''
	};

	proto.removeTransitionStyles = function() {
		// remove transition
		this.css( cleanTransitionStyle );
	};

// ----- stagger ----- //

	proto.stagger = function( delay ) {
		delay = isNaN( delay ) ? 0 : delay;
		this.staggerDelay = delay + 'ms';
	};

// ----- show/hide/remove ----- //

// remove element from DOM
	proto.removeElem = function() {
		this.element.parentNode.removeChild( this.element );
		// remove display: none
		this.css({ display: '' });
		this.emitEvent( 'remove', [ this ] );
	};

	proto.remove = function() {
		// just remove element if no transition support or no transition
		if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) {
			this.removeElem();
			return;
		}

		// start transition
		this.once( 'transitionEnd', function() {
			this.removeElem();
		});
		this.hide();
	};

	proto.reveal = function() {
		delete this.isHidden;
		// remove display: none
		this.css({ display: '' });

		var options = this.layout.options;

		var onTransitionEnd = {};
		var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle');
		onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd;

		this.transition({
			from: options.hiddenStyle,
			to: options.visibleStyle,
			isCleaning: true,
			onTransitionEnd: onTransitionEnd
		});
	};

	proto.onRevealTransitionEnd = function() {
		// check if still visible
		// during transition, item may have been hidden
		if ( !this.isHidden ) {
			this.emitEvent('reveal');
		}
	};

	/**
	 * get style property use for hide/reveal transition end
	 * @param {String} styleProperty - hiddenStyle/visibleStyle
	 * @returns {String}
	 */
	proto.getHideRevealTransitionEndProperty = function( styleProperty ) {
		var optionStyle = this.layout.options[ styleProperty ];
		// use opacity
		if ( optionStyle.opacity ) {
			return 'opacity';
		}
		// get first property
		for ( var prop in optionStyle ) {
			return prop;
		}
	};

	proto.hide = function() {
		// set flag
		this.isHidden = true;
		// remove display: none
		this.css({ display: '' });

		var options = this.layout.options;

		var onTransitionEnd = {};
		var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle');
		onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd;

		this.transition({
			from: options.visibleStyle,
			to: options.hiddenStyle,
			// keep hidden stuff hidden
			isCleaning: true,
			onTransitionEnd: onTransitionEnd
		});
	};

	proto.onHideTransitionEnd = function() {
		// check if still hidden
		// during transition, item may have been un-hidden
		if ( this.isHidden ) {
			this.css({ display: 'none' });
			this.emitEvent('hide');
		}
	};

	proto.destroy = function() {
		this.css({
			position: '',
			left: '',
			right: '',
			top: '',
			bottom: '',
			transition: '',
			transform: ''
		});
	};

	return Item;

}));

/*!
 * Outlayer v2.1.1
 * the brains and guts of a layout library
 * MIT license
 */

( function( window, factory ) {
	'use strict';
	// universal module definition
	/* jshint strict: false */ /* globals define, module, require */
	if ( typeof define == 'function' && define.amd ) {
		// AMD - RequireJS
		define( 'outlayer/outlayer',[
				'ev-emitter/ev-emitter',
				'get-size/get-size',
				'fizzy-ui-utils/utils',
				'./item'
			],
			function( EvEmitter, getSize, utils, Item ) {
				return factory( window, EvEmitter, getSize, utils, Item);
			}
		);
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS - Browserify, Webpack
		module.exports = factory(
			window,
			require('ev-emitter'),
			require('get-size'),
			require('fizzy-ui-utils'),
			require('./item')
		);
	} else {
		// browser global
		window.Outlayer = factory(
			window,
			window.EvEmitter,
			window.getSize,
			window.fizzyUIUtils,
			window.Outlayer.Item
		);
	}

}( window, function factory( window, EvEmitter, getSize, utils, Item ) {
	'use strict';

// ----- vars ----- //

	var console = window.console;
	var jQuery = window.jQuery;
	var noop = function() {};

// -------------------------- Outlayer -------------------------- //

// globally unique identifiers
	var GUID = 0;
// internal store of all Outlayer intances
	var instances = {};


	/**
	 * @param {Element, String} element
	 * @param {Object} options
	 * @constructor
	 */
	function Outlayer( element, options ) {
		var queryElement = utils.getQueryElement( element );
		if ( !queryElement ) {
			if ( console ) {
				console.error( 'Bad element for ' + this.constructor.namespace +
					': ' + ( queryElement || element ) );
			}
			return;
		}
		this.element = queryElement;
		// add jQuery
		if ( jQuery ) {
			this.$element = jQuery( this.element );
		}

		// options
		this.options = utils.extend( {}, this.constructor.defaults );
		this.option( options );

		// add id for Outlayer.getFromElement
		var id = ++GUID;
		this.element.outlayerGUID = id; // expando
		instances[ id ] = this; // associate via id

		// kick it off
		this._create();

		var isInitLayout = this._getOption('initLayout');
		if ( isInitLayout ) {
			this.layout();
		}
	}

// settings are for internal use only
	Outlayer.namespace = 'outlayer';
	Outlayer.Item = Item;

// default options
	Outlayer.defaults = {
		containerStyle: {
			position: 'relative'
		},
		initLayout: true,
		originLeft: true,
		originTop: true,
		resize: true,
		resizeContainer: true,
		// item options
		transitionDuration: '0.4s',
		hiddenStyle: {
			opacity: 0,
			transform: 'scale(0.001)'
		},
		visibleStyle: {
			opacity: 1,
			transform: 'scale(1)'
		}
	};

	var proto = Outlayer.prototype;
// inherit EvEmitter
	utils.extend( proto, EvEmitter.prototype );

	/**
	 * set options
	 * @param {Object} opts
	 */
	proto.option = function( opts ) {
		utils.extend( this.options, opts );
	};

	/**
	 * get backwards compatible option value, check old name
	 */
	proto._getOption = function( option ) {
		var oldOption = this.constructor.compatOptions[ option ];
		return oldOption && this.options[ oldOption ] !== undefined ?
			this.options[ oldOption ] : this.options[ option ];
	};

	Outlayer.compatOptions = {
		// currentName: oldName
		initLayout: 'isInitLayout',
		horizontal: 'isHorizontal',
		layoutInstant: 'isLayoutInstant',
		originLeft: 'isOriginLeft',
		originTop: 'isOriginTop',
		resize: 'isResizeBound',
		resizeContainer: 'isResizingContainer'
	};

	proto._create = function() {
		// get items from children
		this.reloadItems();
		// elements that affect layout, but are not laid out
		this.stamps = [];
		this.stamp( this.options.stamp );
		// set container style
		utils.extend( this.element.style, this.options.containerStyle );

		// bind resize method
		var canBindResize = this._getOption('resize');
		if ( canBindResize ) {
			this.bindResize();
		}
	};

// goes through all children again and gets bricks in proper order
	proto.reloadItems = function() {
		// collection of item elements
		this.items = this._itemize( this.element.children );
	};


	/**
	 * turn elements into Outlayer.Items to be used in layout
	 * @param {Array or NodeList or HTMLElement} elems
	 * @returns {Array} items - collection of new Outlayer Items
	 */
	proto._itemize = function( elems ) {

		var itemElems = this._filterFindItemElements( elems );
		var Item = this.constructor.Item;

		// create new Outlayer Items for collection
		var items = [];
		for ( var i=0; i < itemElems.length; i++ ) {
			var elem = itemElems[i];
			var item = new Item( elem, this );
			items.push( item );
		}

		return items;
	};

	/**
	 * get item elements to be used in layout
	 * @param {Array or NodeList or HTMLElement} elems
	 * @returns {Array} items - item elements
	 */
	proto._filterFindItemElements = function( elems ) {
		return utils.filterFindElements( elems, this.options.itemSelector );
	};

	/**
	 * getter method for getting item elements
	 * @returns {Array} elems - collection of item elements
	 */
	proto.getItemElements = function() {
		return this.items.map( function( item ) {
			return item.element;
		});
	};

// ----- init & layout ----- //

	/**
	 * lays out all items
	 */
	proto.layout = function() {
		this._resetLayout();
		this._manageStamps();

		// don't animate first layout
		var layoutInstant = this._getOption('layoutInstant');
		var isInstant = layoutInstant !== undefined ?
			layoutInstant : !this._isLayoutInited;
		this.layoutItems( this.items, isInstant );

		// flag for initalized
		this._isLayoutInited = true;
	};

// _init is alias for layout
	proto._init = proto.layout;

	/**
	 * logic before any new layout
	 */
	proto._resetLayout = function() {
		this.getSize();
	};


	proto.getSize = function() {
		this.size = getSize( this.element );
	};

	/**
	 * get measurement from option, for columnWidth, rowHeight, gutter
	 * if option is String -> get element from selector string, & get size of element
	 * if option is Element -> get size of element
	 * else use option as a number
	 *
	 * @param {String} measurement
	 * @param {String} size - width or height
	 * @private
	 */
	proto._getMeasurement = function( measurement, size ) {
		var option = this.options[ measurement ];
		var elem;
		if ( !option ) {
			// default to 0
			this[ measurement ] = 0;
		} else {
			// use option as an element
			if ( typeof option == 'string' ) {
				elem = this.element.querySelector( option );
			} else if ( option instanceof HTMLElement ) {
				elem = option;
			}
			// use size of element, if element
			this[ measurement ] = elem ? getSize( elem )[ size ] : option;
		}
	};

	/**
	 * layout a collection of item elements
	 * @api public
	 */
	proto.layoutItems = function( items, isInstant ) {
		items = this._getItemsForLayout( items );

		this._layoutItems( items, isInstant );

		this._postLayout();
	};

	/**
	 * get the items to be laid out
	 * you may want to skip over some items
	 * @param {Array} items
	 * @returns {Array} items
	 */
	proto._getItemsForLayout = function( items ) {
		return items.filter( function( item ) {
			return !item.isIgnored;
		});
	};

	/**
	 * layout items
	 * @param {Array} items
	 * @param {Boolean} isInstant
	 */
	proto._layoutItems = function( items, isInstant ) {
		this._emitCompleteOnItems( 'layout', items );

		if ( !items || !items.length ) {
			// no items, emit event with empty array
			return;
		}

		var queue = [];

		items.forEach( function( item ) {
			// get x/y object from method
			var position = this._getItemLayoutPosition( item );
			// enqueue
			position.item = item;
			position.isInstant = isInstant || item.isLayoutInstant;
			queue.push( position );
		}, this );

		this._processLayoutQueue( queue );
	};

	/**
	 * get item layout position
	 * @param {Outlayer.Item} item
	 * @returns {Object} x and y position
	 */
	proto._getItemLayoutPosition = function( /* item */ ) {
		return {
			x: 0,
			y: 0
		};
	};

	/**
	 * iterate over array and position each item
	 * Reason being - separating this logic prevents 'layout invalidation'
	 * thx @paul_irish
	 * @param {Array} queue
	 */
	proto._processLayoutQueue = function( queue ) {
		this.updateStagger();
		queue.forEach( function( obj, i ) {
			this._positionItem( obj.item, obj.x, obj.y, obj.isInstant, i );
		}, this );
	};

// set stagger from option in milliseconds number
	proto.updateStagger = function() {
		var stagger = this.options.stagger;
		if ( stagger === null || stagger === undefined ) {
			this.stagger = 0;
			return;
		}
		this.stagger = getMilliseconds( stagger );
		return this.stagger;
	};

	/**
	 * Sets position of item in DOM
	 * @param {Outlayer.Item} item
	 * @param {Number} x - horizontal position
	 * @param {Number} y - vertical position
	 * @param {Boolean} isInstant - disables transitions
	 */
	proto._positionItem = function( item, x, y, isInstant, i ) {
		if ( isInstant ) {
			// if not transition, just set CSS
			item.goTo( x, y );
		} else {
			item.stagger( i * this.stagger );
			item.moveTo( x, y );
		}
	};

	/**
	 * Any logic you want to do after each layout,
	 * i.e. size the container
	 */
	proto._postLayout = function() {
		this.resizeContainer();
	};

	proto.resizeContainer = function() {
		var isResizingContainer = this._getOption('resizeContainer');
		if ( !isResizingContainer ) {
			return;
		}
		var size = this._getContainerSize();
		if ( size ) {
			this._setContainerMeasure( size.width, true );
			this._setContainerMeasure( size.height, false );
		}
	};

	/**
	 * Sets width or height of container if returned
	 * @returns {Object} size
	 *   @param {Number} width
	 *   @param {Number} height
	 */
	proto._getContainerSize = noop;

	/**
	 * @param {Number} measure - size of width or height
	 * @param {Boolean} isWidth
	 */
	proto._setContainerMeasure = function( measure, isWidth ) {
		if ( measure === undefined ) {
			return;
		}

		var elemSize = this.size;
		// add padding and border width if border box
		if ( elemSize.isBorderBox ) {
			measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight +
				elemSize.borderLeftWidth + elemSize.borderRightWidth :
				elemSize.paddingBottom + elemSize.paddingTop +
				elemSize.borderTopWidth + elemSize.borderBottomWidth;
		}

		measure = Math.max( measure, 0 );
		this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px';
	};

	/**
	 * emit eventComplete on a collection of items events
	 * @param {String} eventName
	 * @param {Array} items - Outlayer.Items
	 */
	proto._emitCompleteOnItems = function( eventName, items ) {
		var _this = this;
		function onComplete() {
			_this.dispatchEvent( eventName + 'Complete', null, [ items ] );
		}

		var count = items.length;
		if ( !items || !count ) {
			onComplete();
			return;
		}

		var doneCount = 0;
		function tick() {
			doneCount++;
			if ( doneCount == count ) {
				onComplete();
			}
		}

		// bind callback
		items.forEach( function( item ) {
			item.once( eventName, tick );
		});
	};

	/**
	 * emits events via EvEmitter and jQuery events
	 * @param {String} type - name of event
	 * @param {Event} event - original event
	 * @param {Array} args - extra arguments
	 */
	proto.dispatchEvent = function( type, event, args ) {
		// add original event to arguments
		var emitArgs = event ? [ event ].concat( args ) : args;
		this.emitEvent( type, emitArgs );

		if ( jQuery ) {
			// set this.$element
			this.$element = this.$element || jQuery( this.element );
			if ( event ) {
				// create jQuery event
				var $event = jQuery.Event( event );
				$event.type = type;
				this.$element.trigger( $event, args );
			} else {
				// just trigger with type if no event available
				this.$element.trigger( type, args );
			}
		}
	};

// -------------------------- ignore & stamps -------------------------- //


	/**
	 * keep item in collection, but do not lay it out
	 * ignored items do not get skipped in layout
	 * @param {Element} elem
	 */
	proto.ignore = function( elem ) {
		var item = this.getItem( elem );
		if ( item ) {
			item.isIgnored = true;
		}
	};

	/**
	 * return item to layout collection
	 * @param {Element} elem
	 */
	proto.unignore = function( elem ) {
		var item = this.getItem( elem );
		if ( item ) {
			delete item.isIgnored;
		}
	};

	/**
	 * adds elements to stamps
	 * @param {NodeList, Array, Element, or String} elems
	 */
	proto.stamp = function( elems ) {
		elems = this._find( elems );
		if ( !elems ) {
			return;
		}

		this.stamps = this.stamps.concat( elems );
		// ignore
		elems.forEach( this.ignore, this );
	};

	/**
	 * removes elements to stamps
	 * @param {NodeList, Array, or Element} elems
	 */
	proto.unstamp = function( elems ) {
		elems = this._find( elems );
		if ( !elems ){
			return;
		}

		elems.forEach( function( elem ) {
			// filter out removed stamp elements
			utils.removeFrom( this.stamps, elem );
			this.unignore( elem );
		}, this );
	};

	/**
	 * finds child elements
	 * @param {NodeList, Array, Element, or String} elems
	 * @returns {Array} elems
	 */
	proto._find = function( elems ) {
		if ( !elems ) {
			return;
		}
		// if string, use argument as selector string
		if ( typeof elems == 'string' ) {
			elems = this.element.querySelectorAll( elems );
		}
		elems = utils.makeArray( elems );
		return elems;
	};

	proto._manageStamps = function() {
		if ( !this.stamps || !this.stamps.length ) {
			return;
		}

		this._getBoundingRect();

		this.stamps.forEach( this._manageStamp, this );
	};

// update boundingLeft / Top
	proto._getBoundingRect = function() {
		// get bounding rect for container element
		var boundingRect = this.element.getBoundingClientRect();
		var size = this.size;
		this._boundingRect = {
			left: boundingRect.left + size.paddingLeft + size.borderLeftWidth,
			top: boundingRect.top + size.paddingTop + size.borderTopWidth,
			right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ),
			bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth )
		};
	};

	/**
	 * @param {Element} stamp
	 **/
	proto._manageStamp = noop;

	/**
	 * get x/y position of element relative to container element
	 * @param {Element} elem
	 * @returns {Object} offset - has left, top, right, bottom
	 */
	proto._getElementOffset = function( elem ) {
		var boundingRect = elem.getBoundingClientRect();
		var thisRect = this._boundingRect;
		var size = getSize( elem );
		var offset = {
			left: boundingRect.left - thisRect.left - size.marginLeft,
			top: boundingRect.top - thisRect.top - size.marginTop,
			right: thisRect.right - boundingRect.right - size.marginRight,
			bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom
		};
		return offset;
	};

// -------------------------- resize -------------------------- //

// enable event handlers for listeners
// i.e. resize -> onresize
	proto.handleEvent = utils.handleEvent;

	/**
	 * Bind layout to window resizing
	 */
	proto.bindResize = function() {
		window.addEventListener( 'resize', this );
		this.isResizeBound = true;
	};

	/**
	 * Unbind layout to window resizing
	 */
	proto.unbindResize = function() {
		window.removeEventListener( 'resize', this );
		this.isResizeBound = false;
	};

	proto.onresize = function() {
		this.resize();
	};

	utils.debounceMethod( Outlayer, 'onresize', 100 );

	proto.resize = function() {
		// don't trigger if size did not change
		// or if resize was unbound. See #9
		if ( !this.isResizeBound || !this.needsResizeLayout() ) {
			return;
		}

		this.layout();
	};

	/**
	 * check if layout is needed post layout
	 * @returns Boolean
	 */
	proto.needsResizeLayout = function() {
		var size = getSize( this.element );
		// check that this.size and size are there
		// IE8 triggers resize on body size change, so they might not be
		var hasSizes = this.size && size;
		return hasSizes && size.innerWidth !== this.size.innerWidth;
	};

// -------------------------- methods -------------------------- //

	/**
	 * add items to Outlayer instance
	 * @param {Array or NodeList or Element} elems
	 * @returns {Array} items - Outlayer.Items
	 **/
	proto.addItems = function( elems ) {
		var items = this._itemize( elems );
		// add items to collection
		if ( items.length ) {
			this.items = this.items.concat( items );
		}
		return items;
	};

	/**
	 * Layout newly-appended item elements
	 * @param {Array or NodeList or Element} elems
	 */
	proto.appended = function( elems ) {
		var items = this.addItems( elems );
		if ( !items.length ) {
			return;
		}
		// layout and reveal just the new items
		this.layoutItems( items, true );
		this.reveal( items );
	};

	/**
	 * Layout prepended elements
	 * @param {Array or NodeList or Element} elems
	 */
	proto.prepended = function( elems ) {
		var items = this._itemize( elems );
		if ( !items.length ) {
			return;
		}
		// add items to beginning of collection
		var previousItems = this.items.slice(0);
		this.items = items.concat( previousItems );
		// start new layout
		this._resetLayout();
		this._manageStamps();
		// layout new stuff without transition
		this.layoutItems( items, true );
		this.reveal( items );
		// layout previous items
		this.layoutItems( previousItems );
	};

	/**
	 * reveal a collection of items
	 * @param {Array of Outlayer.Items} items
	 */
	proto.reveal = function( items ) {
		this._emitCompleteOnItems( 'reveal', items );
		if ( !items || !items.length ) {
			return;
		}
		var stagger = this.updateStagger();
		items.forEach( function( item, i ) {
			item.stagger( i * stagger );
			item.reveal();
		});
	};

	/**
	 * hide a collection of items
	 * @param {Array of Outlayer.Items} items
	 */
	proto.hide = function( items ) {
		this._emitCompleteOnItems( 'hide', items );
		if ( !items || !items.length ) {
			return;
		}
		var stagger = this.updateStagger();
		items.forEach( function( item, i ) {
			item.stagger( i * stagger );
			item.hide();
		});
	};

	/**
	 * reveal item elements
	 * @param {Array}, {Element}, {NodeList} items
	 */
	proto.revealItemElements = function( elems ) {
		var items = this.getItems( elems );
		this.reveal( items );
	};

	/**
	 * hide item elements
	 * @param {Array}, {Element}, {NodeList} items
	 */
	proto.hideItemElements = function( elems ) {
		var items = this.getItems( elems );
		this.hide( items );
	};

	/**
	 * get Outlayer.Item, given an Element
	 * @param {Element} elem
	 * @param {Function} callback
	 * @returns {Outlayer.Item} item
	 */
	proto.getItem = function( elem ) {
		// loop through items to get the one that matches
		for ( var i=0; i < this.items.length; i++ ) {
			var item = this.items[i];
			if ( item.element == elem ) {
				// return item
				return item;
			}
		}
	};

	/**
	 * get collection of Outlayer.Items, given Elements
	 * @param {Array} elems
	 * @returns {Array} items - Outlayer.Items
	 */
	proto.getItems = function( elems ) {
		elems = utils.makeArray( elems );
		var items = [];
		elems.forEach( function( elem ) {
			var item = this.getItem( elem );
			if ( item ) {
				items.push( item );
			}
		}, this );

		return items;
	};

	/**
	 * remove element(s) from instance and DOM
	 * @param {Array or NodeList or Element} elems
	 */
	proto.remove = function( elems ) {
		var removeItems = this.getItems( elems );

		this._emitCompleteOnItems( 'remove', removeItems );

		// bail if no items to remove
		if ( !removeItems || !removeItems.length ) {
			return;
		}

		removeItems.forEach( function( item ) {
			item.remove();
			// remove item from collection
			utils.removeFrom( this.items, item );
		}, this );
	};

// ----- destroy ----- //

// remove and disable Outlayer instance
	proto.destroy = function() {
		// clean up dynamic styles
		var style = this.element.style;
		style.height = '';
		style.position = '';
		style.width = '';
		// destroy items
		this.items.forEach( function( item ) {
			item.destroy();
		});

		this.unbindResize();

		var id = this.element.outlayerGUID;
		delete instances[ id ]; // remove reference to instance by id
		delete this.element.outlayerGUID;
		// remove data for jQuery
		if ( jQuery ) {
			jQuery.removeData( this.element, this.constructor.namespace );
		}

	};

// -------------------------- data -------------------------- //

	/**
	 * get Outlayer instance from element
	 * @param {Element} elem
	 * @returns {Outlayer}
	 */
	Outlayer.data = function( elem ) {
		elem = utils.getQueryElement( elem );
		var id = elem && elem.outlayerGUID;
		return id && instances[ id ];
	};


// -------------------------- create Outlayer class -------------------------- //

	/**
	 * create a layout class
	 * @param {String} namespace
	 */
	Outlayer.create = function( namespace, options ) {
		// sub-class Outlayer
		var Layout = subclass( Outlayer );
		// apply new options and compatOptions
		Layout.defaults = utils.extend( {}, Outlayer.defaults );
		utils.extend( Layout.defaults, options );
		Layout.compatOptions = utils.extend( {}, Outlayer.compatOptions  );

		Layout.namespace = namespace;

		Layout.data = Outlayer.data;

		// sub-class Item
		Layout.Item = subclass( Item );

		// -------------------------- declarative -------------------------- //

		utils.htmlInit( Layout, namespace );

		// -------------------------- jQuery bridge -------------------------- //

		// make into jQuery plugin
		if ( jQuery && jQuery.bridget ) {
			jQuery.bridget( namespace, Layout );
		}

		return Layout;
	};

	function subclass( Parent ) {
		function SubClass() {
			Parent.apply( this, arguments );
		}

		SubClass.prototype = Object.create( Parent.prototype );
		SubClass.prototype.constructor = SubClass;

		return SubClass;
	}

// ----- helpers ----- //

// how many milliseconds are in each unit
	var msUnits = {
		ms: 1,
		s: 1000
	};

// munge time-like parameter into millisecond number
// '0.4s' -> 40
	function getMilliseconds( time ) {
		if ( typeof time == 'number' ) {
			return time;
		}
		var matches = time.match( /(^\d*\.?\d*)(\w*)/ );
		var num = matches && matches[1];
		var unit = matches && matches[2];
		if ( !num.length ) {
			return 0;
		}
		num = parseFloat( num );
		var mult = msUnits[ unit ] || 1;
		return num * mult;
	}

// ----- fin ----- //

// back in global
	Outlayer.Item = Item;

	return Outlayer;

}));

/**
 * Rect
 * low-level utility class for basic geometry
 */

( function( window, factory ) {
	// universal module definition
	/* jshint strict: false */ /* globals define, module */
	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( 'packery/js/rect',factory );
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory();
	} else {
		// browser global
		window.Packery = window.Packery || {};
		window.Packery.Rect = factory();
	}

}( window, function factory() {
	'use strict';

// -------------------------- Rect -------------------------- //

	function Rect( props ) {
		// extend properties from defaults
		for ( var prop in Rect.defaults ) {
			this[ prop ] = Rect.defaults[ prop ];
		}

		for ( prop in props ) {
			this[ prop ] = props[ prop ];
		}

	}

	Rect.defaults = {
		x: 0,
		y: 0,
		width: 0,
		height: 0
	};

	var proto = Rect.prototype;

	/**
	 * Determines whether or not this rectangle wholly encloses another rectangle or point.
	 * @param {Rect} rect
	 * @returns {Boolean}
	 **/
	proto.contains = function( rect ) {
		// points don't have width or height
		var otherWidth = rect.width || 0;
		var otherHeight = rect.height || 0;
		return this.x <= rect.x &&
			this.y <= rect.y &&
			this.x + this.width >= rect.x + otherWidth &&
			this.y + this.height >= rect.y + otherHeight;
	};

	/**
	 * Determines whether or not the rectangle intersects with another.
	 * @param {Rect} rect
	 * @returns {Boolean}
	 **/
	proto.overlaps = function( rect ) {
		var thisRight = this.x + this.width;
		var thisBottom = this.y + this.height;
		var rectRight = rect.x + rect.width;
		var rectBottom = rect.y + rect.height;

		// http://stackoverflow.com/a/306332
		return this.x < rectRight &&
			thisRight > rect.x &&
			this.y < rectBottom &&
			thisBottom > rect.y;
	};

	/**
	 * @param {Rect} rect - the overlapping rect
	 * @returns {Array} freeRects - rects representing the area around the rect
	 **/
	proto.getMaximalFreeRects = function( rect ) {

		// if no intersection, return false
		if ( !this.overlaps( rect ) ) {
			return false;
		}

		var freeRects = [];
		var freeRect;

		var thisRight = this.x + this.width;
		var thisBottom = this.y + this.height;
		var rectRight = rect.x + rect.width;
		var rectBottom = rect.y + rect.height;

		// top
		if ( this.y < rect.y ) {
			freeRect = new Rect({
				x: this.x,
				y: this.y,
				width: this.width,
				height: rect.y - this.y
			});
			freeRects.push( freeRect );
		}

		// right
		if ( thisRight > rectRight ) {
			freeRect = new Rect({
				x: rectRight,
				y: this.y,
				width: thisRight - rectRight,
				height: this.height
			});
			freeRects.push( freeRect );
		}

		// bottom
		if ( thisBottom > rectBottom ) {
			freeRect = new Rect({
				x: this.x,
				y: rectBottom,
				width: this.width,
				height: thisBottom - rectBottom
			});
			freeRects.push( freeRect );
		}

		// left
		if ( this.x < rect.x ) {
			freeRect = new Rect({
				x: this.x,
				y: this.y,
				width: rect.x - this.x,
				height: this.height
			});
			freeRects.push( freeRect );
		}

		return freeRects;
	};

	proto.canFit = function( rect ) {
		return this.width >= rect.width && this.height >= rect.height;
	};

	return Rect;

}));

/**
 * Packer
 * bin-packing algorithm
 */

( function( window, factory ) {
	// universal module definition
	/* jshint strict: false */ /* globals define, module, require */
	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( 'packery/js/packer',[ './rect' ], factory );
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory(
			require('./rect')
		);
	} else {
		// browser global
		var Packery = window.Packery = window.Packery || {};
		Packery.Packer = factory( Packery.Rect );
	}

}( window, function factory( Rect ) {
	'use strict';

// -------------------------- Packer -------------------------- //

	/**
	 * @param {Number} width
	 * @param {Number} height
	 * @param {String} sortDirection
	 *   topLeft for vertical, leftTop for horizontal
	 */
	function Packer( width, height, sortDirection ) {
		this.width = width || 0;
		this.height = height || 0;
		this.sortDirection = sortDirection || 'downwardLeftToRight';

		this.reset();
	}

	var proto = Packer.prototype;

	proto.reset = function() {
		this.spaces = [];

		var initialSpace = new Rect({
			x: 0,
			y: 0,
			width: this.width,
			height: this.height
		});

		this.spaces.push( initialSpace );
		// set sorter
		this.sorter = sorters[ this.sortDirection ] || sorters.downwardLeftToRight;
	};

// change x and y of rect to fit with in Packer's available spaces
	proto.pack = function( rect ) {
		for ( var i=0; i < this.spaces.length; i++ ) {
			var space = this.spaces[i];
			if ( space.canFit( rect ) ) {
				this.placeInSpace( rect, space );
				break;
			}
		}
	};

	proto.columnPack = function( rect ) {
		for ( var i=0; i < this.spaces.length; i++ ) {
			var space = this.spaces[i];
			var canFitInSpaceColumn = space.x <= rect.x &&
				space.x + space.width >= rect.x + rect.width &&
				space.height >= rect.height - 0.01; // fudge number for rounding error
			if ( canFitInSpaceColumn ) {
				rect.y = space.y;
				this.placed( rect );
				break;
			}
		}
	};

	proto.rowPack = function( rect ) {
		for ( var i=0; i < this.spaces.length; i++ ) {
			var space = this.spaces[i];
			var canFitInSpaceRow = space.y <= rect.y &&
				space.y + space.height >= rect.y + rect.height &&
				space.width >= rect.width - 0.01; // fudge number for rounding error
			if ( canFitInSpaceRow ) {
				rect.x = space.x;
				this.placed( rect );
				break;
			}
		}
	};

	proto.placeInSpace = function( rect, space ) {
		// place rect in space
		rect.x = space.x;
		rect.y = space.y;

		this.placed( rect );
	};

// update spaces with placed rect
	proto.placed = function( rect ) {
		// update spaces
		var revisedSpaces = [];
		for ( var i=0; i < this.spaces.length; i++ ) {
			var space = this.spaces[i];
			var newSpaces = space.getMaximalFreeRects( rect );
			// add either the original space or the new spaces to the revised spaces
			if ( newSpaces ) {
				revisedSpaces.push.apply( revisedSpaces, newSpaces );
			} else {
				revisedSpaces.push( space );
			}
		}

		this.spaces = revisedSpaces;

		this.mergeSortSpaces();
	};

	proto.mergeSortSpaces = function() {
		// remove redundant spaces
		Packer.mergeRects( this.spaces );
		this.spaces.sort( this.sorter );
	};

// add a space back
	proto.addSpace = function( rect ) {
		this.spaces.push( rect );
		this.mergeSortSpaces();
	};

// -------------------------- utility functions -------------------------- //

	/**
	 * Remove redundant rectangle from array of rectangles
	 * @param {Array} rects: an array of Rects
	 * @returns {Array} rects: an array of Rects
	 **/
	Packer.mergeRects = function( rects ) {
		var i = 0;
		var rect = rects[i];

		rectLoop:
			while ( rect ) {
				var j = 0;
				var compareRect = rects[ i + j ];

				while ( compareRect ) {
					if  ( compareRect == rect ) {
						j++; // next
					} else if ( compareRect.contains( rect ) ) {
						// remove rect
						rects.splice( i, 1 );
						rect = rects[i]; // set next rect
						continue rectLoop; // bail on compareLoop
					} else if ( rect.contains( compareRect ) ) {
						// remove compareRect
						rects.splice( i + j, 1 );
					} else {
						j++;
					}
					compareRect = rects[ i + j ]; // set next compareRect
				}
				i++;
				rect = rects[i];
			}

		return rects;
	};


// -------------------------- sorters -------------------------- //

// functions for sorting rects in order
	var sorters = {
		// top down, then left to right
		downwardLeftToRight: function( a, b ) {
			return a.y - b.y || a.x - b.x;
		},
		// left to right, then top down
		rightwardTopToBottom: function( a, b ) {
			return a.x - b.x || a.y - b.y;
		}
	};


// --------------------------  -------------------------- //

	return Packer;

}));

/**
 * Packery Item Element
 **/

( function( window, factory ) {
	// universal module definition
	/* jshint strict: false */ /* globals define, module, require */
	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( 'packery/js/item',[
				'outlayer/outlayer',
				'./rect'
			],
			factory );
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory(
			require('outlayer'),
			require('./rect')
		);
	} else {
		// browser global
		window.Packery.Item = factory(
			window.Outlayer,
			window.Packery.Rect
		);
	}

}( window, function factory( Outlayer, Rect ) {
	'use strict';

// -------------------------- Item -------------------------- //

	var docElemStyle = document.documentElement.style;

	var transformProperty = typeof docElemStyle.transform == 'string' ?
		'transform' : 'WebkitTransform';

// sub-class Item
	var Item = function PackeryItem() {
		Outlayer.Item.apply( this, arguments );
	};

	var proto = Item.prototype = Object.create( Outlayer.Item.prototype );

	var __create = proto._create;
	proto._create = function() {
		// call default _create logic
		__create.call( this );
		this.rect = new Rect();
	};

	var _moveTo = proto.moveTo;
	proto.moveTo = function( x, y ) {
		// don't shift 1px while dragging
		var dx = Math.abs( this.position.x - x );
		var dy = Math.abs( this.position.y - y );

		var canHackGoTo = this.layout.dragItemCount && !this.isPlacing &&
			!this.isTransitioning && dx < 1 && dy < 1;
		if ( canHackGoTo ) {
			this.goTo( x, y );
			return;
		}
		_moveTo.apply( this, arguments );
	};

// -------------------------- placing -------------------------- //

	proto.enablePlacing = function() {
		this.removeTransitionStyles();
		// remove transform property from transition
		if ( this.isTransitioning && transformProperty ) {
			this.element.style[ transformProperty ] = 'none';
		}
		this.isTransitioning = false;
		this.getSize();
		this.layout._setRectSize( this.element, this.rect );
		this.isPlacing = true;
	};

	proto.disablePlacing = function() {
		this.isPlacing = false;
	};

// -----  ----- //

// remove element from DOM
	proto.removeElem = function() {
		var parent = this.element.parentNode;
		if ( parent ) {
			parent.removeChild( this.element );
		}
		// add space back to packer
		this.layout.packer.addSpace( this.rect );
		this.emitEvent( 'remove', [ this ] );
	};

// ----- dropPlaceholder ----- //

	proto.showDropPlaceholder = function() {
		var dropPlaceholder = this.dropPlaceholder;
		if ( !dropPlaceholder ) {
			// create dropPlaceholder
			dropPlaceholder = this.dropPlaceholder = document.createElement('div');
			dropPlaceholder.className = 'packery-drop-placeholder';
			dropPlaceholder.style.position = 'absolute';
		}

		dropPlaceholder.style.width = this.size.width + 'px';
		dropPlaceholder.style.height = this.size.height + 'px';
		this.positionDropPlaceholder();
		this.layout.element.appendChild( dropPlaceholder );
	};

	proto.positionDropPlaceholder = function() {
		this.dropPlaceholder.style[ transformProperty ] = 'translate(' +
			this.rect.x + 'px, ' + this.rect.y + 'px)';
	};

	proto.hideDropPlaceholder = function() {
		// only remove once, #333
		var parent = this.dropPlaceholder.parentNode;
		if ( parent ) {
			parent.removeChild( this.dropPlaceholder );
		}
	};

// -----  ----- //

	return Item;

}));

/*!
 * Packery v2.1.2
 * Gapless, draggable grid layouts
 *
 * Licensed GPLv3 for open source use
 * or Packery Commercial License for commercial use
 *
 * http://packery.metafizzy.co
 * Copyright 2013-2018 Metafizzy
 */

( function( window, factory ) {
	// universal module definition
	/* jshint strict: false */ /* globals define, module, require */
	if ( typeof define == 'function' && define.amd ) {
		// AMD
		define( [
				'get-size/get-size',
				'outlayer/outlayer',
				'packery/js/rect',
				'packery/js/packer',
				'packery/js/item'
			],
			factory );
	} else if ( typeof module == 'object' && module.exports ) {
		// CommonJS
		module.exports = factory(
			require('get-size'),
			require('outlayer'),
			require('./rect'),
			require('./packer'),
			require('./item')
		);
	} else {
		// browser global
		window.Packery = factory(
			window.getSize,
			window.Outlayer,
			window.Packery.Rect,
			window.Packery.Packer,
			window.Packery.Item
		);
	}

}( window, function factory( getSize, Outlayer, Rect, Packer, Item ) {
	'use strict';

// ----- Rect ----- //

// allow for pixel rounding errors IE8-IE11 & Firefox; #227
	Rect.prototype.canFit = function( rect ) {
		return this.width >= rect.width - 1 && this.height >= rect.height - 1;
	};

// -------------------------- Packery -------------------------- //

// create an Outlayer layout class
	var Packery = Outlayer.create('packery');
	Packery.Item = Item;

	var proto = Packery.prototype;

	proto._create = function() {
		// call super
		Outlayer.prototype._create.call( this );

		// initial properties
		this.packer = new Packer();
		// packer for drop targets
		this.shiftPacker = new Packer();
		this.isEnabled = true;

		this.dragItemCount = 0;

		// create drag handlers
		var _this = this;
		this.handleDraggabilly = {
			dragStart: function() {
				_this.itemDragStart( this.element );
			},
			dragMove: function() {
				_this.itemDragMove( this.element, this.position.x, this.position.y );
			},
			dragEnd: function() {
				_this.itemDragEnd( this.element );
			}
		};

		this.handleUIDraggable = {
			start: function handleUIDraggableStart( event, ui ) {
				// HTML5 may trigger dragstart, dismiss HTML5 dragging
				if ( !ui ) {
					return;
				}
				_this.itemDragStart( event.currentTarget );
			},
			drag: function handleUIDraggableDrag( event, ui ) {
				if ( !ui ) {
					return;
				}
				_this.itemDragMove( event.currentTarget, ui.position.left, ui.position.top );
			},
			stop: function handleUIDraggableStop( event, ui ) {
				if ( !ui ) {
					return;
				}
				_this.itemDragEnd( event.currentTarget );
			}
		};

	};


// ----- init & layout ----- //

	/**
	 * logic before any new layout
	 */
	proto._resetLayout = function() {
		this.getSize();

		this._getMeasurements();

		// reset packer
		var width, height, sortDirection;
		// packer settings, if horizontal or vertical
		if ( this._getOption('horizontal') ) {
			width = Infinity;
			height = this.size.innerHeight + this.gutter;
			sortDirection = 'rightwardTopToBottom';
		} else {
			width = this.size.innerWidth + this.gutter;
			height = Infinity;
			sortDirection = 'downwardLeftToRight';
		}

		this.packer.width = this.shiftPacker.width = width;
		this.packer.height = this.shiftPacker.height = height;
		this.packer.sortDirection = this.shiftPacker.sortDirection = sortDirection;

		this.packer.reset();

		// layout
		this.maxY = 0;
		this.maxX = 0;
	};

	/**
	 * update columnWidth, rowHeight, & gutter
	 * @private
	 */
	proto._getMeasurements = function() {
		this._getMeasurement( 'columnWidth', 'width' );
		this._getMeasurement( 'rowHeight', 'height' );
		this._getMeasurement( 'gutter', 'width' );
	};

	proto._getItemLayoutPosition = function( item ) {
		this._setRectSize( item.element, item.rect );
		if ( this.isShifting || this.dragItemCount > 0 ) {
			var packMethod = this._getPackMethod();
			this.packer[ packMethod ]( item.rect );
		} else {
			this.packer.pack( item.rect );
		}

		this._setMaxXY( item.rect );
		return item.rect;
	};

	proto.shiftLayout = function() {
		this.isShifting = true;
		this.layout();
		delete this.isShifting;
	};

	proto._getPackMethod = function() {
		return this._getOption('horizontal') ? 'rowPack' : 'columnPack';
	};


	/**
	 * set max X and Y value, for size of container
	 * @param {Packery.Rect} rect
	 * @private
	 */
	proto._setMaxXY = function( rect ) {
		this.maxX = Math.max( rect.x + rect.width, this.maxX );
		this.maxY = Math.max( rect.y + rect.height, this.maxY );
	};

	/**
	 * set the width and height of a rect, applying columnWidth and rowHeight
	 * @param {Element} elem
	 * @param {Packery.Rect} rect
	 */
	proto._setRectSize = function( elem, rect ) {
		var size = getSize( elem );
		var w = size.outerWidth;
		var h = size.outerHeight;
		// size for columnWidth and rowHeight, if available
		// only check if size is non-zero, #177
		if ( w || h ) {
			w = this._applyGridGutter( w, this.columnWidth );
			h = this._applyGridGutter( h, this.rowHeight );
		}
		// rect must fit in packer
		rect.width = Math.min( w, this.packer.width );
		rect.height = Math.min( h, this.packer.height );
	};

	/**
	 * fits item to columnWidth/rowHeight and adds gutter
	 * @param {Number} measurement - item width or height
	 * @param {Number} gridSize - columnWidth or rowHeight
	 * @returns measurement
	 */
	proto._applyGridGutter = function( measurement, gridSize ) {
		// just add gutter if no gridSize
		if ( !gridSize ) {
			return measurement + this.gutter;
		}
		gridSize += this.gutter;
		// fit item to columnWidth/rowHeight
		var remainder = measurement % gridSize;
		var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
		measurement = Math[ mathMethod ]( measurement / gridSize ) * gridSize;
		return measurement;
	};

	proto._getContainerSize = function() {
		if ( this._getOption('horizontal') ) {
			return {
				width: this.maxX - this.gutter
			};
		} else {
			return {
				height: this.maxY - this.gutter
			};
		}
	};


// -------------------------- stamp -------------------------- //

	/**
	 * makes space for element
	 * @param {Element} elem
	 */
	proto._manageStamp = function( elem ) {

		var item = this.getItem( elem );
		var rect;
		if ( item && item.isPlacing ) {
			rect = item.rect;
		} else {
			var offset = this._getElementOffset( elem );
			rect = new Rect({
				x: this._getOption('originLeft') ? offset.left : offset.right,
				y: this._getOption('originTop') ? offset.top : offset.bottom
			});
		}

		this._setRectSize( elem, rect );
		// save its space in the packer
		this.packer.placed( rect );
		this._setMaxXY( rect );
	};

// -------------------------- methods -------------------------- //

	function verticalSorter( a, b ) {
		return a.position.y - b.position.y || a.position.x - b.position.x;
	}

	function horizontalSorter( a, b ) {
		return a.position.x - b.position.x || a.position.y - b.position.y;
	}

	proto.sortItemsByPosition = function() {
		var sorter = this._getOption('horizontal') ? horizontalSorter : verticalSorter;
		this.items.sort( sorter );
	};

	/**
	 * Fit item element in its current position
	 * Packery will position elements around it
	 * useful for expanding elements
	 *
	 * @param {Element} elem
	 * @param {Number} x - horizontal destination position, optional
	 * @param {Number} y - vertical destination position, optional
	 */
	proto.fit = function( elem, x, y ) {
		var item = this.getItem( elem );
		if ( !item ) {
			return;
		}

		// stamp item to get it out of layout
		this.stamp( item.element );
		// set placing flag
		item.enablePlacing();
		this.updateShiftTargets( item );
		// fall back to current position for fitting
		x = x === undefined ? item.rect.x: x;
		y = y === undefined ? item.rect.y: y;
		// position it best at its destination
		this.shift( item, x, y );
		this._bindFitEvents( item );
		item.moveTo( item.rect.x, item.rect.y );
		// layout everything else
		this.shiftLayout();
		// return back to regularly scheduled programming
		this.unstamp( item.element );
		this.sortItemsByPosition();
		item.disablePlacing();
	};

	/**
	 * emit event when item is fit and other items are laid out
	 * @param {Packery.Item} item
	 * @private
	 */
	proto._bindFitEvents = function( item ) {
		var _this = this;
		var ticks = 0;
		function onLayout() {
			ticks++;
			if ( ticks != 2 ) {
				return;
			}
			_this.dispatchEvent( 'fitComplete', null, [ item ] );
		}
		// when item is laid out
		item.once( 'layout', onLayout );
		// when all items are laid out
		this.once( 'layoutComplete', onLayout );
	};

// -------------------------- resize -------------------------- //

// debounced, layout on resize
	proto.resize = function() {
		// don't trigger if size did not change
		// or if resize was unbound. See #285, outlayer#9
		if ( !this.isResizeBound || !this.needsResizeLayout() ) {
			return;
		}

		if ( this.options.shiftPercentResize ) {
			this.resizeShiftPercentLayout();
		} else {
			this.layout();
		}
	};

	/**
	 * check if layout is needed post layout
	 * @returns Boolean
	 */
	proto.needsResizeLayout = function() {
		var size = getSize( this.element );
		var innerSize = this._getOption('horizontal') ? 'innerHeight' : 'innerWidth';
		return size[ innerSize ] != this.size[ innerSize ];
	};

	proto.resizeShiftPercentLayout = function() {
		var items = this._getItemsForLayout( this.items );

		var isHorizontal = this._getOption('horizontal');
		var coord = isHorizontal ? 'y' : 'x';
		var measure = isHorizontal ? 'height' : 'width';
		var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth';
		var innerSize = isHorizontal ? 'innerHeight' : 'innerWidth';

		// proportional re-align items
		var previousSegment = this[ segmentName ];
		previousSegment = previousSegment && previousSegment + this.gutter;

		if ( previousSegment ) {
			this._getMeasurements();
			var currentSegment = this[ segmentName ] + this.gutter;
			items.forEach( function( item ) {
				var seg = Math.round( item.rect[ coord ] / previousSegment );
				item.rect[ coord ] = seg * currentSegment;
			});
		} else {
			var currentSize = getSize( this.element )[ innerSize ] + this.gutter;
			var previousSize = this.packer[ measure ];
			items.forEach( function( item ) {
				item.rect[ coord ] = ( item.rect[ coord ] / previousSize ) * currentSize;
			});
		}

		this.shiftLayout();
	};

// -------------------------- drag -------------------------- //

	/**
	 * handle an item drag start event
	 * @param {Element} elem
	 */
	proto.itemDragStart = function( elem ) {
		if ( !this.isEnabled ) {
			return;
		}
		this.stamp( elem );
		// this.ignore( elem );
		var item = this.getItem( elem );
		if ( !item ) {
			return;
		}

		item.enablePlacing();
		item.showDropPlaceholder();
		this.dragItemCount++;
		this.updateShiftTargets( item );
	};

	proto.updateShiftTargets = function( dropItem ) {
		this.shiftPacker.reset();

		// pack stamps
		this._getBoundingRect();
		var isOriginLeft = this._getOption('originLeft');
		var isOriginTop = this._getOption('originTop');
		this.stamps.forEach( function( stamp ) {
			// ignore dragged item
			var item = this.getItem( stamp );
			if ( item && item.isPlacing ) {
				return;
			}
			var offset = this._getElementOffset( stamp );
			var rect = new Rect({
				x: isOriginLeft ? offset.left : offset.right,
				y: isOriginTop ? offset.top : offset.bottom
			});
			this._setRectSize( stamp, rect );
			// save its space in the packer
			this.shiftPacker.placed( rect );
		}, this );

		// reset shiftTargets
		var isHorizontal = this._getOption('horizontal');
		var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth';
		var measure = isHorizontal ? 'height' : 'width';

		this.shiftTargetKeys = [];
		this.shiftTargets = [];
		var boundsSize;
		var segment = this[ segmentName ];
		segment = segment && segment + this.gutter;

		if ( segment ) {
			var segmentSpan = Math.ceil( dropItem.rect[ measure ] / segment );
			var segs = Math.floor( ( this.shiftPacker[ measure ] + this.gutter ) / segment );
			boundsSize = ( segs - segmentSpan ) * segment;
			// add targets on top
			for ( var i=0; i < segs; i++ ) {
				var initialX = isHorizontal ? 0 : i * segment;
				var initialY = isHorizontal ? i * segment : 0;
				this._addShiftTarget( initialX, initialY, boundsSize );
			}
		} else {
			boundsSize = ( this.shiftPacker[ measure ] + this.gutter ) - dropItem.rect[ measure ];
			this._addShiftTarget( 0, 0, boundsSize );
		}

		// pack each item to measure where shiftTargets are
		var items = this._getItemsForLayout( this.items );
		var packMethod = this._getPackMethod();
		items.forEach( function( item ) {
			var rect = item.rect;
			this._setRectSize( item.element, rect );
			this.shiftPacker[ packMethod ]( rect );

			// add top left corner
			this._addShiftTarget( rect.x, rect.y, boundsSize );
			// add bottom left / top right corner
			var cornerX = isHorizontal ? rect.x + rect.width : rect.x;
			var cornerY = isHorizontal ? rect.y : rect.y + rect.height;
			this._addShiftTarget( cornerX, cornerY, boundsSize );

			if ( segment ) {
				// add targets for each column on bottom / row on right
				var segSpan = Math.round( rect[ measure ] / segment );
				for ( var i=1; i < segSpan; i++ ) {
					var segX = isHorizontal ? cornerX : rect.x + segment * i;
					var segY = isHorizontal ? rect.y + segment * i : cornerY;
					this._addShiftTarget( segX, segY, boundsSize );
				}
			}
		}, this );

	};

	proto._addShiftTarget = function( x, y, boundsSize ) {
		var checkCoord = this._getOption('horizontal') ? y : x;
		if ( checkCoord !== 0 && checkCoord > boundsSize ) {
			return;
		}
		// create string for a key, easier to keep track of what targets
		var key = x + ',' + y;
		var hasKey = this.shiftTargetKeys.indexOf( key ) != -1;
		if ( hasKey ) {
			return;
		}
		this.shiftTargetKeys.push( key );
		this.shiftTargets.push({ x: x, y: y });
	};

// -------------------------- drop -------------------------- //

	proto.shift = function( item, x, y ) {
		var shiftPosition;
		var minDistance = Infinity;
		var position = { x: x, y: y };
		this.shiftTargets.forEach( function( target ) {
			var distance = getDistance( target, position );
			if ( distance < minDistance ) {
				shiftPosition = target;
				minDistance = distance;
			}
		});
		item.rect.x = shiftPosition.x;
		item.rect.y = shiftPosition.y;
	};

	function getDistance( a, b ) {
		var dx = b.x - a.x;
		var dy = b.y - a.y;
		return Math.sqrt( dx * dx + dy * dy );
	}

// -------------------------- drag move -------------------------- //

	var DRAG_THROTTLE_TIME = 120;

	/**
	 * handle an item drag move event
	 * @param {Element} elem
	 * @param {Number} x - horizontal change in position
	 * @param {Number} y - vertical change in position
	 */
	proto.itemDragMove = function( elem, x, y ) {
		var item = this.isEnabled && this.getItem( elem );
		if ( !item ) {
			return;
		}

		x -= this.size.paddingLeft;
		y -= this.size.paddingTop;

		var _this = this;
		function onDrag() {
			_this.shift( item, x, y );
			item.positionDropPlaceholder();
			_this.layout();
		}

		// throttle
		var now = new Date();
		var isThrottled = this._itemDragTime && now - this._itemDragTime < DRAG_THROTTLE_TIME;
		if ( isThrottled ) {
			clearTimeout( this.dragTimeout );
			this.dragTimeout = setTimeout( onDrag, DRAG_THROTTLE_TIME );
		} else {
			onDrag();
			this._itemDragTime = now;
		}
	};

// -------------------------- drag end -------------------------- //

	/**
	 * handle an item drag end event
	 * @param {Element} elem
	 */
	proto.itemDragEnd = function( elem ) {
		var item = this.isEnabled && this.getItem( elem );
		if ( !item ) {
			return;
		}

		clearTimeout( this.dragTimeout );
		item.element.classList.add('is-positioning-post-drag');

		var completeCount = 0;
		var _this = this;
		function onDragEndLayoutComplete() {
			completeCount++;
			if ( completeCount != 2 ) {
				return;
			}
			// reset drag item
			item.element.classList.remove('is-positioning-post-drag');
			item.hideDropPlaceholder();
			_this.dispatchEvent( 'dragItemPositioned', null, [ item ] );
		}

		item.once( 'layout', onDragEndLayoutComplete );
		this.once( 'layoutComplete', onDragEndLayoutComplete );
		item.moveTo( item.rect.x, item.rect.y );
		this.layout();
		this.dragItemCount = Math.max( 0, this.dragItemCount - 1 );
		this.sortItemsByPosition();
		item.disablePlacing();
		this.unstamp( item.element );
	};

	/**
	 * binds Draggabilly events
	 * @param {Draggabilly} draggie
	 */
	proto.bindDraggabillyEvents = function( draggie ) {
		this._bindDraggabillyEvents( draggie, 'on' );
	};

	proto.unbindDraggabillyEvents = function( draggie ) {
		this._bindDraggabillyEvents( draggie, 'off' );
	};

	proto._bindDraggabillyEvents = function( draggie, method ) {
		var handlers = this.handleDraggabilly;
		draggie[ method ]( 'dragStart', handlers.dragStart );
		draggie[ method ]( 'dragMove', handlers.dragMove );
		draggie[ method ]( 'dragEnd', handlers.dragEnd );
	};

	/**
	 * binds jQuery UI Draggable events
	 * @param {jQuery} $elems
	 */
	proto.bindUIDraggableEvents = function( $elems ) {
		this._bindUIDraggableEvents( $elems, 'on' );
	};

	proto.unbindUIDraggableEvents = function( $elems ) {
		this._bindUIDraggableEvents( $elems, 'off' );
	};

	proto._bindUIDraggableEvents = function( $elems, method ) {
		var handlers = this.handleUIDraggable;
		$elems
			[ method ]( 'dragstart', handlers.start )
			[ method ]( 'drag', handlers.drag )
			[ method ]( 'dragstop', handlers.stop );
	};

// ----- destroy ----- //

	var _destroy = proto.destroy;
	proto.destroy = function() {
		_destroy.apply( this, arguments );
		// disable flag; prevent drag events from triggering. #72
		this.isEnabled = false;
	};

// -----  ----- //

	Packery.Rect = Rect;
	Packery.Packer = Packer;

	return Packery;

}));


/**
 * JavaScript Client Detection
 * (C) viazenetti GmbH (Christian Ludwig)
 */
(function (window) {
    {
        var unknown = '-';

        // screen
        var screenSize = '';
        if (screen.width) {
            width = (screen.width) ? screen.width : '';
            height = (screen.height) ? screen.height : '';
            screenSize += '' + width + " x " + height;
        }

        // browser
        var nVer = navigator.appVersion;
        var nAgt = navigator.userAgent;
        var browser = navigator.appName;
        var version = '' + parseFloat(navigator.appVersion);
        var majorVersion = parseInt(navigator.appVersion, 10);
        var nameOffset, verOffset, ix;

        // Opera
        if ((verOffset = nAgt.indexOf('Opera')) != -1) {
            browser = 'Opera';
            version = nAgt.substring(verOffset + 6);
            if ((verOffset = nAgt.indexOf('Version')) != -1) {
                version = nAgt.substring(verOffset + 8);
            }
        }
        // Opera Next
        if ((verOffset = nAgt.indexOf('OPR')) != -1) {
            browser = 'Opera';
            version = nAgt.substring(verOffset + 4);
        }
        // Legacy Edge
        else if ((verOffset = nAgt.indexOf('Edge')) != -1) {
            browser = 'Edge';
            version = nAgt.substring(verOffset + 5);
        }
        // Edge (Chromium)
        else if ((verOffset = nAgt.indexOf('Edg')) != -1) {
            browser = 'Microsoft Edge';
            version = nAgt.substring(verOffset + 4);
        }
        // MSIE
        else if ((verOffset = nAgt.indexOf('MSIE')) != -1) {
            browser = 'Internet';
            version = nAgt.substring(verOffset + 5);
        }
        // Chrome
        else if ((verOffset = nAgt.indexOf('Chrome')) != -1) {
            browser = 'Chrome';
            version = nAgt.substring(verOffset + 7);
        }
        // Safari
        else if ((verOffset = nAgt.indexOf('Safari')) != -1) {
            browser = 'Safari';
            version = nAgt.substring(verOffset + 7);
            if ((verOffset = nAgt.indexOf('Version')) != -1) {
                version = nAgt.substring(verOffset + 8);
            }
        }
        // Firefox
        else if ((verOffset = nAgt.indexOf('Firefox')) != -1) {
            browser = 'Firefox';
            version = nAgt.substring(verOffset + 8);
        }
        // MSIE 11+
        else if (nAgt.indexOf('Trident/') != -1) {
            browser = 'Internet';
            version = nAgt.substring(nAgt.indexOf('rv:') + 3);
        }
        // Other browsers
        else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
            browser = nAgt.substring(nameOffset, verOffset);
            version = nAgt.substring(verOffset + 1);
            if (browser.toLowerCase() == browser.toUpperCase()) {
                browser = navigator.appName;
            }
        }
        // trim the version string
        if ((ix = version.indexOf(';')) != -1) version = version.substring(0, ix);
        if ((ix = version.indexOf(' ')) != -1) version = version.substring(0, ix);
        if ((ix = version.indexOf(')')) != -1) version = version.substring(0, ix);

        majorVersion = parseInt('' + version, 10);
        if (isNaN(majorVersion)) {
            version = '' + parseFloat(navigator.appVersion);
            majorVersion = parseInt(navigator.appVersion, 10);
        }

        // mobile version
        var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer);

        // system
        var os = unknown;
        var clientStrings = [
            {s:'Windows 10', r:/(Windows 10.0|Windows NT 10.0)/},
            {s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/},
            {s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/},
            {s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/},
            {s:'Windows Vista', r:/Windows NT 6.0/},
            {s:'Windows Server 2003', r:/Windows NT 5.2/},
            {s:'Windows XP', r:/(Windows NT 5.1|Windows XP)/},
            {s:'Windows 2000', r:/(Windows NT 5.0|Windows 2000)/},
            {s:'Windows ME', r:/(Win 9x 4.90|Windows ME)/},
            {s:'Windows 98', r:/(Windows 98|Win98)/},
            {s:'Windows 95', r:/(Windows 95|Win95|Windows_95)/},
            {s:'Windows NT 4.0', r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
            {s:'Windows CE', r:/Windows CE/},
            {s:'Windows 3.11', r:/Win16/},
            {s:'Android', r:/Android/},
            {s:'Open BSD', r:/OpenBSD/},
            {s:'Sun OS', r:/SunOS/},
            {s:'Chrome OS', r:/CrOS/},
            {s:'Linux', r:/(Linux|X11(?!.*CrOS))/},
            {s:'iOS', r:/(iPhone|iPad|iPod)/},
            {s:'Mac OS X', r:/Mac OS X/},
            {s:'Mac OS', r:/(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
            {s:'QNX', r:/QNX/},
            {s:'UNIX', r:/UNIX/},
            {s:'BeOS', r:/BeOS/},
            {s:'OS/2', r:/OS\/2/},
            {s:'Search Bot', r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}
        ];
        for (var id in clientStrings) {
            var cs = clientStrings[id];
            if (cs.r.test(nAgt)) {
                os = cs.s;
                break;
            }
        }

        var osVersion = unknown;

        if (/Windows/.test(os)) {
            osVersion = /Windows (.*)/.exec(os)[1];
            os = 'Windows';
        }

        switch (os) {
            case 'Mac OS':
            case 'Mac OS X':
            case 'Android':
                osVersion = /(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([\.\_\d]+)/.exec(nAgt)[1];
                break;

            case 'iOS':
                osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
                osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
                break;
        }

        // flash (you'll need to include swfobject)
        /* script src="//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js" */
        var flashVersion = 'no check';
        if (typeof swfobject != 'undefined') {
            var fv = swfobject.getFlashPlayerVersion();
            if (fv.major > 0) {
                flashVersion = fv.major + '.' + fv.minor + ' r' + fv.release;
            }
            else  {
                flashVersion = unknown;
            }
        }
    }

    window.jscd = {
        screen: screenSize,
        browser: browser,
        browserVersion: version,
        browserMajorVersion: majorVersion,
        mobile: mobile,
        os: os,
        osVersion: osVersion,
        flashVersion: flashVersion
    };
}(this));

(function($) {

	var $html = $('html');

	$html.addClass('browser-' + jscd.browser.replaceAll(' ', '-'));
	$html.addClass('platform-' + jscd.os);
	
})(jQuery);
/*!
 * The Final Countdown for jQuery v2.1.0 (http://hilios.github.io/jQuery.countdown/)
 * Copyright (c) 2015 Edson Hilios
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
(function (factory) {
    "use strict";
    if (typeof define === "function" && define.amd) {
        define(["jquery"], factory);
    } else {
        factory(jQuery);
    }
})(function ($) {
    "use strict";
    var instances = [], matchers = [], defaultOptions = {
        precision: 100,
        elapse: false
    };
    matchers.push(/^[0-9]*$/.source);
    matchers.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source);
    matchers.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source);
    matchers = new RegExp(matchers.join("|"));
    function parseDateString(dateString) {
        if (dateString instanceof Date) {
            return dateString;
        }
        if (String(dateString).match(matchers)) {
            if (String(dateString).match(/^[0-9]*$/)) {
                dateString = Number(dateString);
            }
            if (String(dateString).match(/\-/)) {
                dateString = String(dateString).replace(/\-/g, "/");
            }
            return new Date(dateString);
        } else {
            throw new Error("Couldn't cast `" + dateString + "` to a date object.");
        }
    }
    var DIRECTIVE_KEY_MAP = {
        Y: "years",
        m: "months",
        n: "daysToMonth",
        w: "weeks",
        d: "daysToWeek",
        D: "totalDays",
        H: "hours",
        M: "minutes",
        S: "seconds"
    };
    function escapedRegExp(str) {
        var sanitize = str.toString().replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
        return new RegExp(sanitize);
    }
    function strftime(offsetObject) {
        return function (format) {
            var directives = format.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);
            if (directives) {
                for (var i = 0, len = directives.length; i < len; ++i) {
                    var directive = directives[i].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/), regexp = escapedRegExp(directive[0]), modifier = directive[1] || "", plural = directive[3] || "", value = null;
                    directive = directive[2];
                    if (DIRECTIVE_KEY_MAP.hasOwnProperty(directive)) {
                        value = DIRECTIVE_KEY_MAP[directive];
                        value = Number(offsetObject[value]);
                    }
                    if (value !== null) {
                        if (modifier === "!") {
                            value = pluralize(plural, value);
                        }
                        if (modifier === "") {
                            if (value < 10) {
                                value = "0" + value.toString();
                            }
                        }
                        format = format.replace(regexp, value.toString());
                    }
                }
            }
            format = format.replace(/%%/, "%");
            return format;
        };
    }
    function pluralize(format, count) {
        var plural = "s", singular = "";
        if (format) {
            format = format.replace(/(:|;|\s)/gi, "").split(/\,/);
            if (format.length === 1) {
                plural = format[0];
            } else {
                singular = format[0];
                plural = format[1];
            }
        }
        if (Math.abs(count) === 1) {
            return singular;
        } else {
            return plural;
        }
    }
    var Countdown = function (el, finalDate, options) {
        this.el = el;
        this.$el = $(el);
        this.interval = null;
        this.offset = {};
        this.options = $.extend({}, defaultOptions);
        this.instanceNumber = instances.length;
        instances.push(this);
        this.$el.data("countdown-instance", this.instanceNumber);
        if (options) {
            if (typeof options === "function") {
                this.$el.on("update.countdown", options);
                this.$el.on("stoped.countdown", options);
                this.$el.on("finish.countdown", options);
            } else {
                this.options = $.extend({}, defaultOptions, options);
            }
        }
        this.setFinalDate(finalDate);
        this.start();
    };
    $.extend(Countdown.prototype, {
        start: function () {
            if (this.interval !== null) {
                clearInterval(this.interval);
            }
            var self = this;
            this.update();
            this.interval = setInterval(function () {
                self.update.call(self);
            }, this.options.precision);
        },
        stop: function () {
            clearInterval(this.interval);
            this.interval = null;
            this.dispatchEvent("stoped");
        },
        toggle: function () {
            if (this.interval) {
                this.stop();
            } else {
                this.start();
            }
        },
        pause: function () {
            this.stop();
        },
        resume: function () {
            this.start();
        },
        remove: function () {
            this.stop.call(this);
            instances[this.instanceNumber] = null;
            delete this.$el.data().countdownInstance;
        },
        setFinalDate: function (value) {
            this.finalDate = parseDateString(value);
        },
        update: function () {
            if (this.$el.closest("html").length === 0) {
                this.remove();
                return;
            }
            var hasEventsAttached = $._data(this.el, "events") !== undefined, now = new Date(), newTotalSecsLeft;
            newTotalSecsLeft = this.finalDate.getTime() - now.getTime();
            newTotalSecsLeft = Math.ceil(newTotalSecsLeft / 1e3);
            newTotalSecsLeft = !this.options.elapse && newTotalSecsLeft < 0 ? 0 : Math.abs(newTotalSecsLeft);
            if (this.totalSecsLeft === newTotalSecsLeft || !hasEventsAttached) {
                return;
            } else {
                this.totalSecsLeft = newTotalSecsLeft;
            }
            this.elapsed = now >= this.finalDate;
            this.offset = {
                seconds: this.totalSecsLeft % 60,
                minutes: Math.floor(this.totalSecsLeft / 60) % 60,
                hours: Math.floor(this.totalSecsLeft / 60 / 60) % 24,
                days: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
                daysToWeek: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
                daysToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 % 30.4368),
                totalDays: Math.floor(this.totalSecsLeft / 60 / 60 / 24),
                weeks: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7),
                months: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 30.4368),
                years: Math.abs(this.finalDate.getFullYear() - now.getFullYear())
            };
            if (!this.options.elapse && this.totalSecsLeft === 0) {
                this.stop();
                this.dispatchEvent("finish");
            } else {
                this.dispatchEvent("update");
            }
        },
        dispatchEvent: function (eventName) {
            var event = $.Event(eventName + ".countdown");
            event.finalDate = this.finalDate;
            event.elapsed = this.elapsed;
            event.offset = $.extend({}, this.offset);
            event.strftime = strftime(this.offset);
            this.$el.trigger(event);
        }
    });
    $.fn.countdown = function () {
        var argumentsArray = Array.prototype.slice.call(arguments, 0);
        return this.each(function () {
            var instanceNumber = $(this).data("countdown-instance");
            if (instanceNumber !== undefined) {
                var instance = instances[instanceNumber], method = argumentsArray[0];
                if (Countdown.prototype.hasOwnProperty(method)) {
                    instance[method].apply(instance, argumentsArray.slice(1));
                } else if (String(method).match(/^[$A-Z_][0-9A-Z_$]*$/i) === null) {
                    instance.setFinalDate.call(instance, method);
                    instance.start();
                } else {
                    $.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi, method));
                }
            } else {
                new Countdown(this, argumentsArray[0], argumentsArray[1]);
            }
        });
    };
});

!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.dayjs=e()}(this,function(){"use strict";var t="millisecond",e="second",n="minute",r="hour",i="day",s="week",u="month",a="quarter",o="year",f="date",h=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d+)?$/,c=/\[([^\]]+)]|Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,d={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},$=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},l={s:$,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+$(r,2,"0")+":"+$(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return-t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,u),s=n-i<0,a=e.clone().add(r+(s?-1:1),u);return+(-(r+(n-i)/(s?i-a:a-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(h){return{M:u,y:o,w:s,d:i,D:f,h:r,m:n,s:e,ms:t,Q:a}[h]||String(h||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},y="en",M={};M[y]=d;var m=function(t){return t instanceof S},D=function(t,e,n){var r;if(!t)return y;if("string"==typeof t)M[t]&&(r=t),e&&(M[t]=e,r=t);else{var i=t.name;M[i]=t,r=i}return!n&&r&&(y=r),r||!n&&y},v=function(t,e){if(m(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new S(n)},g=l;g.l=D,g.i=m,g.w=function(t,e){return v(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var S=function(){function d(t){this.$L=D(t.locale,null,!0),this.parse(t)}var $=d.prototype;return $.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(g.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(h);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},$.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},$.$utils=function(){return g},$.isValid=function(){return!("Invalid Date"===this.$d.toString())},$.isSame=function(t,e){var n=v(t);return this.startOf(e)<=n&&n<=this.endOf(e)},$.isAfter=function(t,e){return v(t)<this.startOf(e)},$.isBefore=function(t,e){return this.endOf(e)<v(t)},$.$g=function(t,e,n){return g.u(t)?this[e]:this.set(n,t)},$.unix=function(){return Math.floor(this.valueOf()/1e3)},$.valueOf=function(){return this.$d.getTime()},$.startOf=function(t,a){var h=this,c=!!g.u(a)||a,d=g.p(t),$=function(t,e){var n=g.w(h.$u?Date.UTC(h.$y,e,t):new Date(h.$y,e,t),h);return c?n:n.endOf(i)},l=function(t,e){return g.w(h.toDate()[t].apply(h.toDate("s"),(c?[0,0,0,0]:[23,59,59,999]).slice(e)),h)},y=this.$W,M=this.$M,m=this.$D,D="set"+(this.$u?"UTC":"");switch(d){case o:return c?$(1,0):$(31,11);case u:return c?$(1,M):$(0,M+1);case s:var v=this.$locale().weekStart||0,S=(y<v?y+7:y)-v;return $(c?m-S:m+(6-S),M);case i:case f:return l(D+"Hours",0);case r:return l(D+"Minutes",1);case n:return l(D+"Seconds",2);case e:return l(D+"Milliseconds",3);default:return this.clone()}},$.endOf=function(t){return this.startOf(t,!1)},$.$set=function(s,a){var h,c=g.p(s),d="set"+(this.$u?"UTC":""),$=(h={},h[i]=d+"Date",h[f]=d+"Date",h[u]=d+"Month",h[o]=d+"FullYear",h[r]=d+"Hours",h[n]=d+"Minutes",h[e]=d+"Seconds",h[t]=d+"Milliseconds",h)[c],l=c===i?this.$D+(a-this.$W):a;if(c===u||c===o){var y=this.clone().set(f,1);y.$d[$](l),y.init(),this.$d=y.set(f,Math.min(this.$D,y.daysInMonth())).$d}else $&&this.$d[$](l);return this.init(),this},$.set=function(t,e){return this.clone().$set(t,e)},$.get=function(t){return this[g.p(t)]()},$.add=function(t,a){var f,h=this;t=Number(t);var c=g.p(a),d=function(e){var n=v(h);return g.w(n.date(n.date()+Math.round(e*t)),h)};if(c===u)return this.set(u,this.$M+t);if(c===o)return this.set(o,this.$y+t);if(c===i)return d(1);if(c===s)return d(7);var $=(f={},f[n]=6e4,f[r]=36e5,f[e]=1e3,f)[c]||1,l=this.$d.getTime()+t*$;return g.w(l,this)},$.subtract=function(t,e){return this.add(-1*t,e)},$.format=function(t){var e=this;if(!this.isValid())return"Invalid Date";var n=t||"YYYY-MM-DDTHH:mm:ssZ",r=g.z(this),i=this.$locale(),s=this.$H,u=this.$m,a=this.$M,o=i.weekdays,f=i.months,h=function(t,r,i,s){return t&&(t[r]||t(e,n))||i[r].substr(0,s)},d=function(t){return g.s(s%12||12,t,"0")},$=i.meridiem||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r},l={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:g.s(a+1,2,"0"),MMM:h(i.monthsShort,a,f,3),MMMM:h(f,a),D:this.$D,DD:g.s(this.$D,2,"0"),d:String(this.$W),dd:h(i.weekdaysMin,this.$W,o,2),ddd:h(i.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(s),HH:g.s(s,2,"0"),h:d(1),hh:d(2),a:$(s,u,!0),A:$(s,u,!1),m:String(u),mm:g.s(u,2,"0"),s:String(this.$s),ss:g.s(this.$s,2,"0"),SSS:g.s(this.$ms,3,"0"),Z:r};return n.replace(c,function(t,e){return e||l[t]||r.replace(":","")})},$.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},$.diff=function(t,f,h){var c,d=g.p(f),$=v(t),l=6e4*($.utcOffset()-this.utcOffset()),y=this-$,M=g.m(this,$);return M=(c={},c[o]=M/12,c[u]=M,c[a]=M/3,c[s]=(y-l)/6048e5,c[i]=(y-l)/864e5,c[r]=y/36e5,c[n]=y/6e4,c[e]=y/1e3,c)[d]||y,h?M:g.a(M)},$.daysInMonth=function(){return this.endOf(u).$D},$.$locale=function(){return M[this.$L]},$.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=D(t,e,!0);return r&&(n.$L=r),n},$.clone=function(){return g.w(this.$d,this)},$.toDate=function(){return new Date(this.valueOf())},$.toJSON=function(){return this.isValid()?this.toISOString():null},$.toISOString=function(){return this.$d.toISOString()},$.toString=function(){return this.$d.toUTCString()},d}(),p=S.prototype;return v.prototype=p,[["$ms",t],["$s",e],["$m",n],["$H",r],["$W",i],["$M",u],["$y",o],["$D",f]].forEach(function(t){p[t[1]]=function(e){return this.$g(e,t[0],t[1])}}),v.extend=function(t,e){return t(e,S,v),v},v.locale=D,v.isDayjs=m,v.unix=function(t){return v(1e3*t)},v.en=M[y],v.Ls=M,v.p={},v});

!function(a,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((a=a||self).timezoneSupport={})}(this,function(a){"use strict";function l(a){return 96<a?a-87:64<a?a-29:a-48}function r(a){var i=a.split("."),e=i[0],r=i[1]||"",A=1,c=0,o=0,n=1;45===a.charCodeAt(0)&&(n=-(c=1));for(var t=c,s=e.length;t<s;++t){o=60*o+l(e.charCodeAt(t))}for(var u=0,m=r.length;u<m;++u){o+=l(r.charCodeAt(u))*(A/=60)}return o*n}function t(a){for(var i=0,e=a.length;i<e;++i)a[i]=r(a[i])}function s(a,i){for(var e=[],r=0,A=i.length;r<A;++r)e[r]=a[i[r]];return e}function A(a){var i=a.split("|"),e=i[2].split(" "),r=i[3].split(""),A=i[4].split(" ");t(e),t(r),t(A),function(a,i){for(var e=0;e<i;++e)a[e]=Math.round((a[e-1]||0)+6e4*a[e]);a[i-1]=1/0}(A,r.length);var c=i[0],o=s(i[1].split(" "),r),n=0|i[5];return{name:c,abbreviations:o,offsets:e=s(e,r),untils:A,population:n}}var c,i,o,n;function d(a){var i=a.year,e=a.month,r=a.day,A=a.hours,c=void 0===A?0:A,o=a.minutes,n=void 0===o?0:o,t=a.seconds,s=void 0===t?0:t,u=a.milliseconds,m=void 0===u?0:u;return Date.UTC(i,e-1,r,c,n,s,m)}function E(a){return{year:a.getUTCFullYear(),month:a.getUTCMonth()+1,day:a.getUTCDate(),dayOfWeek:a.getUTCDay(),hours:a.getUTCHours(),minutes:a.getUTCMinutes(),seconds:a.getUTCSeconds()||0,milliseconds:a.getUTCMilliseconds()||0}}function h(a){return{year:a.getFullYear(),month:a.getMonth()+1,day:a.getDate(),dayOfWeek:a.getDay(),hours:a.getHours(),minutes:a.getMinutes(),seconds:a.getSeconds()||0,milliseconds:a.getMilliseconds()||0}}function T(a,i){var e=function(a,i){for(var e=i.untils,r=0,A=e.length;r<A;++r)if(a<e[r])return r}(a,i);return{abbreviation:i.abbreviations[e],offset:i.offsets[e]}}function z(a,i){Object.defineProperty(a,"epoch",{value:i})}var e,u,m;u=(e={version:"2019a",zones:["Africa/Abidjan|GMT|0|0||48e5","Africa/Nairobi|EAT|-30|0||47e5","Africa/Algiers|CET|-10|0||26e5","Africa/Lagos|WAT|-10|0||17e6","Africa/Maputo|CAT|-20|0||26e5","Africa/Cairo|EET EEST|-20 -30|01010|1M2m0 gL0 e10 mn0|15e6","Africa/Casablanca|+00 +01|0 -10|010101010101010101010101010101010101|1H3C0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 28M0 e00 2600 e00 28M0 e00 2600 gM0|32e5","Europe/Paris|CET CEST|-10 -20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|11e6","Africa/Johannesburg|SAST|-20|0||84e5","Africa/Khartoum|EAT CAT|-30 -20|01|1Usl0|51e5","Africa/Sao_Tome|GMT WAT|0 -10|010|1UQN0 2q00","Africa/Tripoli|EET CET CEST|-20 -10 -20|0120|1IlA0 TA0 1o00|11e5","Africa/Windhoek|CAT WAT|-20 -10|0101010101010|1GQo0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0|32e4","America/Adak|HST HDT|a0 90|01010101010101010101010|1GIc0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|326","America/Anchorage|AKST AKDT|90 80|01010101010101010101010|1GIb0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|30e4","America/Santo_Domingo|AST|40|0||29e5","America/Araguaina|-03 -02|30 20|010|1IdD0 Lz0|14e4","America/Fortaleza|-03|30|0||34e5","America/Asuncion|-03 -04|30 40|01010101010101010101010|1GTf0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0|28e5","America/Panama|EST|50|0||15e5","America/Mexico_City|CST CDT|60 50|01010101010101010101010|1GQw0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0|20e6","America/Bahia|-02 -03|20 30|01|1GCq0|27e5","America/Managua|CST|60|0||22e5","America/La_Paz|-04|40|0||19e5","America/Lima|-05|50|0||11e6","America/Denver|MST MDT|70 60|01010101010101010101010|1GI90 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|26e5","America/Campo_Grande|-03 -04|30 40|01010101010101010101010|1GCr0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1HB0 FX0 1HB0 FX0 1HB0 IL0 1HB0 FX0 1HB0|77e4","America/Cancun|CST CDT EST|60 50 50|01010102|1GQw0 1nX0 14p0 1lb0 14p0 1lb0 Dd0|63e4","America/Caracas|-0430 -04|4u 40|01|1QMT0|29e5","America/Chicago|CST CDT|60 50|01010101010101010101010|1GI80 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|92e5","America/Chihuahua|MST MDT|70 60|01010101010101010101010|1GQx0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0|81e4","America/Phoenix|MST|70|0||42e5","America/Los_Angeles|PST PDT|80 70|01010101010101010101010|1GIa0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|15e6","America/New_York|EST EDT|50 40|01010101010101010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|21e6","America/Rio_Branco|-04 -05|40 50|01|1KLE0|31e4","America/Fort_Nelson|PST PDT MST|80 70 70|01010102|1GIa0 1zb0 Op0 1zb0 Op0 1zb0 Op0|39e2","America/Halifax|AST ADT|40 30|01010101010101010101010|1GI60 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|39e4","America/Godthab|-03 -02|30 20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|17e3","America/Grand_Turk|EST EDT AST|50 40 40|0101010121010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 5Ip0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|37e2","America/Havana|CST CDT|50 40|01010101010101010101010|1GQt0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0|21e5","America/Metlakatla|PST AKST AKDT|80 90 80|01212120121212121|1PAa0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 uM0 jB0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|14e2","America/Miquelon|-03 -02|30 20|01010101010101010101010|1GI50 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|61e2","America/Montevideo|-02 -03|20 30|01010101|1GI40 1o10 11z0 1o10 11z0 1o10 11z0|17e5","America/Noronha|-02|20|0||30e2","America/Port-au-Prince|EST EDT|50 40|010101010101010101010|1GI70 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 3iN0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|23e5","Antarctica/Palmer|-03 -04|30 40|010101010|1H3D0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0|40","America/Santiago|-03 -04|30 40|010101010101010101010|1H3D0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1zb0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0|62e5","America/Sao_Paulo|-02 -03|20 30|01010101010101010101010|1GCq0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1HB0 FX0 1HB0 FX0 1HB0 IL0 1HB0 FX0 1HB0|20e6","Atlantic/Azores|-01 +00|10 0|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|25e4","America/St_Johns|NST NDT|3u 2u|01010101010101010101010|1GI5u 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0|11e4","Antarctica/Casey|+11 +08|-b0 -80|0101|1GAF0 blz0 3m10|10","Antarctica/Davis|+05 +07|-50 -70|01|1GAI0|70","Pacific/Port_Moresby|+10|-a0|0||25e4","Pacific/Guadalcanal|+11|-b0|0||11e4","Asia/Tashkent|+05|-50|0||23e5","Pacific/Auckland|NZDT NZST|-d0 -c0|01010101010101010101010|1GQe0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00|14e5","Asia/Baghdad|+03|-30|0||66e5","Antarctica/Troll|+00 +02|0 -20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|40","Asia/Dhaka|+06|-60|0||16e6","Asia/Amman|EET EEST|-20 -30|010101010101010101010|1GPy0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00|25e5","Asia/Kamchatka|+12|-c0|0||18e4","Asia/Baku|+04 +05|-40 -50|010101010|1GNA0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00|27e5","Asia/Bangkok|+07|-70|0||15e6","Asia/Barnaul|+07 +06|-70 -60|010|1N7v0 3rd0","Asia/Beirut|EET EEST|-20 -30|01010101010101010101010|1GNy0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0|22e5","Asia/Kuala_Lumpur|+08|-80|0||71e5","Asia/Kolkata|IST|-5u|0||15e6","Asia/Chita|+10 +08 +09|-a0 -80 -90|012|1N7s0 3re0|33e4","Asia/Ulaanbaatar|+08 +09|-80 -90|01010|1O8G0 1cJ0 1cP0 1cJ0|12e5","Asia/Shanghai|CST|-80|0||23e6","Asia/Colombo|+0530|-5u|0||22e5","Asia/Damascus|EET EEST|-20 -30|01010101010101010101010|1GPy0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0|26e5","Asia/Dili|+09|-90|0||19e4","Asia/Dubai|+04|-40|0||39e5","Asia/Famagusta|EET EEST +03|-20 -30 -30|0101010101201010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 15U0 2Ks0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0","Asia/Gaza|EET EEST|-20 -30|01010101010101010101010|1GPy0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0|18e5","Asia/Hong_Kong|HKT|-80|0||73e5","Asia/Hovd|+07 +08|-70 -80|01010|1O8H0 1cJ0 1cP0 1cJ0|81e3","Asia/Irkutsk|+09 +08|-90 -80|01|1N7t0|60e4","Europe/Istanbul|EET EEST +03|-20 -30 -30|01010101012|1GNB0 1qM0 11A0 1o00 1200 1nA0 11A0 1tA0 U00 15w0|13e6","Asia/Jakarta|WIB|-70|0||31e6","Asia/Jayapura|WIT|-90|0||26e4","Asia/Jerusalem|IST IDT|-20 -30|01010101010101010101010|1GPA0 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0|81e4","Asia/Kabul|+0430|-4u|0||46e5","Asia/Karachi|PKT|-50|0||24e6","Asia/Kathmandu|+0545|-5J|0||12e5","Asia/Yakutsk|+10 +09|-a0 -90|01|1N7s0|28e4","Asia/Krasnoyarsk|+08 +07|-80 -70|01|1N7u0|10e5","Asia/Magadan|+12 +10 +11|-c0 -a0 -b0|012|1N7q0 3Cq0|95e3","Asia/Makassar|WITA|-80|0||15e5","Asia/Manila|PST|-80|0||24e6","Europe/Athens|EET EEST|-20 -30|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|35e5","Asia/Novosibirsk|+07 +06|-70 -60|010|1N7v0 4eN0|15e5","Asia/Omsk|+07 +06|-70 -60|01|1N7v0|12e5","Asia/Pyongyang|KST KST|-90 -8u|010|1P4D0 6BA0|29e5","Asia/Qyzylorda|+06 +05|-60 -50|01|1Xei0|73e4","Asia/Rangoon|+0630|-6u|0||48e5","Asia/Sakhalin|+11 +10|-b0 -a0|010|1N7r0 3rd0|58e4","Asia/Seoul|KST|-90|0||23e6","Asia/Srednekolymsk|+12 +11|-c0 -b0|01|1N7q0|35e2","Asia/Tehran|+0330 +0430|-3u -4u|01010101010101010101010|1GLUu 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0|14e6","Asia/Tokyo|JST|-90|0||38e6","Asia/Tomsk|+07 +06|-70 -60|010|1N7v0 3Qp0|10e5","Asia/Vladivostok|+11 +10|-b0 -a0|01|1N7r0|60e4","Asia/Yekaterinburg|+06 +05|-60 -50|01|1N7w0|14e5","Europe/Lisbon|WET WEST|0 -10|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|27e5","Atlantic/Cape_Verde|-01|10|0||50e4","Australia/Sydney|AEDT AEST|-b0 -a0|01010101010101010101010|1GQg0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0|40e5","Australia/Adelaide|ACDT ACST|-au -9u|01010101010101010101010|1GQgu 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0|11e5","Australia/Brisbane|AEST|-a0|0||20e5","Australia/Darwin|ACST|-9u|0||12e4","Australia/Eucla|+0845|-8J|0||368","Australia/Lord_Howe|+11 +1030|-b0 -au|01010101010101010101010|1GQf0 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu|347","Australia/Perth|AWST|-80|0||18e5","Pacific/Easter|-05 -06|50 60|010101010101010101010|1H3D0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1zb0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0|30e2","Europe/Dublin|GMT IST|0 -10|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|12e5","Etc/GMT-1|+01|-10|0|","Pacific/Fakaofo|+13|-d0|0||483","Pacific/Kiritimati|+14|-e0|0||51e2","Etc/GMT-2|+02|-20|0|","Pacific/Tahiti|-10|a0|0||18e4","Pacific/Niue|-11|b0|0||12e2","Etc/GMT+12|-12|c0|0|","Pacific/Galapagos|-06|60|0||25e3","Etc/GMT+7|-07|70|0|","Pacific/Pitcairn|-08|80|0||56","Pacific/Gambier|-09|90|0||125","Etc/UTC|UTC|0|0|","Europe/Ulyanovsk|+04 +03|-40 -30|010|1N7y0 3rd0|13e5","Europe/London|GMT BST|0 -10|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|10e6","Europe/Chisinau|EET EEST|-20 -30|01010101010101010101010|1GNA0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|67e4","Europe/Kaliningrad|+03 EET|-30 -20|01|1N7z0|44e4","Europe/Kirov|+04 +03|-40 -30|01|1N7y0|48e4","Europe/Moscow|MSK MSK|-40 -30|01|1N7y0|16e6","Europe/Saratov|+04 +03|-40 -30|010|1N7y0 5810","Europe/Simferopol|EET EEST MSK MSK|-20 -30 -40 -30|0101023|1GNB0 1qM0 11A0 1o00 11z0 1nW0|33e4","Europe/Volgograd|+04 +03|-40 -30|010|1N7y0 9Jd0|10e5","Pacific/Honolulu|HST|a0|0||37e4","MET|MET MEST|-10 -20|01010101010101010101010|1GNB0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0","Pacific/Chatham|+1345 +1245|-dJ -cJ|01010101010101010101010|1GQe0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00|600","Pacific/Apia|+14 +13|-e0 -d0|01010101010101010101010|1GQe0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00|37e3","Pacific/Bougainville|+10 +11|-a0 -b0|01|1NwE0|18e4","Pacific/Fiji|+13 +12|-d0 -c0|01010101010101010101010|1Goe0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1VA0 s00 1VA0|88e4","Pacific/Guam|ChST|-a0|0||17e4","Pacific/Marquesas|-0930|9u|0||86e2","Pacific/Pago_Pago|SST|b0|0||37e2","Pacific/Norfolk|+1130 +11|-bu -b0|01|1PoCu|25e4","Pacific/Tongatapu|+13 +14|-d0 -e0|010|1S4d0 s00|75e3"],links:["Africa/Abidjan|Africa/Accra","Africa/Abidjan|Africa/Bamako","Africa/Abidjan|Africa/Banjul","Africa/Abidjan|Africa/Bissau","Africa/Abidjan|Africa/Conakry","Africa/Abidjan|Africa/Dakar","Africa/Abidjan|Africa/Freetown","Africa/Abidjan|Africa/Lome","Africa/Abidjan|Africa/Monrovia","Africa/Abidjan|Africa/Nouakchott","Africa/Abidjan|Africa/Ouagadougou","Africa/Abidjan|Africa/Timbuktu","Africa/Abidjan|America/Danmarkshavn","Africa/Abidjan|Atlantic/Reykjavik","Africa/Abidjan|Atlantic/St_Helena","Africa/Abidjan|Etc/GMT","Africa/Abidjan|Etc/GMT+0","Africa/Abidjan|Etc/GMT-0","Africa/Abidjan|Etc/GMT0","Africa/Abidjan|Etc/Greenwich","Africa/Abidjan|GMT","Africa/Abidjan|GMT+0","Africa/Abidjan|GMT-0","Africa/Abidjan|GMT0","Africa/Abidjan|Greenwich","Africa/Abidjan|Iceland","Africa/Algiers|Africa/Tunis","Africa/Cairo|Egypt","Africa/Casablanca|Africa/El_Aaiun","Africa/Johannesburg|Africa/Maseru","Africa/Johannesburg|Africa/Mbabane","Africa/Lagos|Africa/Bangui","Africa/Lagos|Africa/Brazzaville","Africa/Lagos|Africa/Douala","Africa/Lagos|Africa/Kinshasa","Africa/Lagos|Africa/Libreville","Africa/Lagos|Africa/Luanda","Africa/Lagos|Africa/Malabo","Africa/Lagos|Africa/Ndjamena","Africa/Lagos|Africa/Niamey","Africa/Lagos|Africa/Porto-Novo","Africa/Maputo|Africa/Blantyre","Africa/Maputo|Africa/Bujumbura","Africa/Maputo|Africa/Gaborone","Africa/Maputo|Africa/Harare","Africa/Maputo|Africa/Kigali","Africa/Maputo|Africa/Lubumbashi","Africa/Maputo|Africa/Lusaka","Africa/Nairobi|Africa/Addis_Ababa","Africa/Nairobi|Africa/Asmara","Africa/Nairobi|Africa/Asmera","Africa/Nairobi|Africa/Dar_es_Salaam","Africa/Nairobi|Africa/Djibouti","Africa/Nairobi|Africa/Juba","Africa/Nairobi|Africa/Kampala","Africa/Nairobi|Africa/Mogadishu","Africa/Nairobi|Indian/Antananarivo","Africa/Nairobi|Indian/Comoro","Africa/Nairobi|Indian/Mayotte","Africa/Tripoli|Libya","America/Adak|America/Atka","America/Adak|US/Aleutian","America/Anchorage|America/Juneau","America/Anchorage|America/Nome","America/Anchorage|America/Sitka","America/Anchorage|America/Yakutat","America/Anchorage|US/Alaska","America/Campo_Grande|America/Cuiaba","America/Chicago|America/Indiana/Knox","America/Chicago|America/Indiana/Tell_City","America/Chicago|America/Knox_IN","America/Chicago|America/Matamoros","America/Chicago|America/Menominee","America/Chicago|America/North_Dakota/Beulah","America/Chicago|America/North_Dakota/Center","America/Chicago|America/North_Dakota/New_Salem","America/Chicago|America/Rainy_River","America/Chicago|America/Rankin_Inlet","America/Chicago|America/Resolute","America/Chicago|America/Winnipeg","America/Chicago|CST6CDT","America/Chicago|Canada/Central","America/Chicago|US/Central","America/Chicago|US/Indiana-Starke","America/Chihuahua|America/Mazatlan","America/Chihuahua|Mexico/BajaSur","America/Denver|America/Boise","America/Denver|America/Cambridge_Bay","America/Denver|America/Edmonton","America/Denver|America/Inuvik","America/Denver|America/Ojinaga","America/Denver|America/Shiprock","America/Denver|America/Yellowknife","America/Denver|Canada/Mountain","America/Denver|MST7MDT","America/Denver|Navajo","America/Denver|US/Mountain","America/Fortaleza|America/Argentina/Buenos_Aires","America/Fortaleza|America/Argentina/Catamarca","America/Fortaleza|America/Argentina/ComodRivadavia","America/Fortaleza|America/Argentina/Cordoba","America/Fortaleza|America/Argentina/Jujuy","America/Fortaleza|America/Argentina/La_Rioja","America/Fortaleza|America/Argentina/Mendoza","America/Fortaleza|America/Argentina/Rio_Gallegos","America/Fortaleza|America/Argentina/Salta","America/Fortaleza|America/Argentina/San_Juan","America/Fortaleza|America/Argentina/San_Luis","America/Fortaleza|America/Argentina/Tucuman","America/Fortaleza|America/Argentina/Ushuaia","America/Fortaleza|America/Belem","America/Fortaleza|America/Buenos_Aires","America/Fortaleza|America/Catamarca","America/Fortaleza|America/Cayenne","America/Fortaleza|America/Cordoba","America/Fortaleza|America/Jujuy","America/Fortaleza|America/Maceio","America/Fortaleza|America/Mendoza","America/Fortaleza|America/Paramaribo","America/Fortaleza|America/Recife","America/Fortaleza|America/Rosario","America/Fortaleza|America/Santarem","America/Fortaleza|Antarctica/Rothera","America/Fortaleza|Atlantic/Stanley","America/Fortaleza|Etc/GMT+3","America/Halifax|America/Glace_Bay","America/Halifax|America/Goose_Bay","America/Halifax|America/Moncton","America/Halifax|America/Thule","America/Halifax|Atlantic/Bermuda","America/Halifax|Canada/Atlantic","America/Havana|Cuba","America/La_Paz|America/Boa_Vista","America/La_Paz|America/Guyana","America/La_Paz|America/Manaus","America/La_Paz|America/Porto_Velho","America/La_Paz|Brazil/West","America/La_Paz|Etc/GMT+4","America/Lima|America/Bogota","America/Lima|America/Guayaquil","America/Lima|Etc/GMT+5","America/Los_Angeles|America/Dawson","America/Los_Angeles|America/Ensenada","America/Los_Angeles|America/Santa_Isabel","America/Los_Angeles|America/Tijuana","America/Los_Angeles|America/Vancouver","America/Los_Angeles|America/Whitehorse","America/Los_Angeles|Canada/Pacific","America/Los_Angeles|Canada/Yukon","America/Los_Angeles|Mexico/BajaNorte","America/Los_Angeles|PST8PDT","America/Los_Angeles|US/Pacific","America/Los_Angeles|US/Pacific-New","America/Managua|America/Belize","America/Managua|America/Costa_Rica","America/Managua|America/El_Salvador","America/Managua|America/Guatemala","America/Managua|America/Regina","America/Managua|America/Swift_Current","America/Managua|America/Tegucigalpa","America/Managua|Canada/Saskatchewan","America/Mexico_City|America/Bahia_Banderas","America/Mexico_City|America/Merida","America/Mexico_City|America/Monterrey","America/Mexico_City|Mexico/General","America/New_York|America/Detroit","America/New_York|America/Fort_Wayne","America/New_York|America/Indiana/Indianapolis","America/New_York|America/Indiana/Marengo","America/New_York|America/Indiana/Petersburg","America/New_York|America/Indiana/Vevay","America/New_York|America/Indiana/Vincennes","America/New_York|America/Indiana/Winamac","America/New_York|America/Indianapolis","America/New_York|America/Iqaluit","America/New_York|America/Kentucky/Louisville","America/New_York|America/Kentucky/Monticello","America/New_York|America/Louisville","America/New_York|America/Montreal","America/New_York|America/Nassau","America/New_York|America/Nipigon","America/New_York|America/Pangnirtung","America/New_York|America/Thunder_Bay","America/New_York|America/Toronto","America/New_York|Canada/Eastern","America/New_York|EST5EDT","America/New_York|US/East-Indiana","America/New_York|US/Eastern","America/New_York|US/Michigan","America/Noronha|Atlantic/South_Georgia","America/Noronha|Brazil/DeNoronha","America/Noronha|Etc/GMT+2","America/Panama|America/Atikokan","America/Panama|America/Cayman","America/Panama|America/Coral_Harbour","America/Panama|America/Jamaica","America/Panama|EST","America/Panama|Jamaica","America/Phoenix|America/Creston","America/Phoenix|America/Dawson_Creek","America/Phoenix|America/Hermosillo","America/Phoenix|MST","America/Phoenix|US/Arizona","America/Rio_Branco|America/Eirunepe","America/Rio_Branco|America/Porto_Acre","America/Rio_Branco|Brazil/Acre","America/Santiago|Chile/Continental","America/Santo_Domingo|America/Anguilla","America/Santo_Domingo|America/Antigua","America/Santo_Domingo|America/Aruba","America/Santo_Domingo|America/Barbados","America/Santo_Domingo|America/Blanc-Sablon","America/Santo_Domingo|America/Curacao","America/Santo_Domingo|America/Dominica","America/Santo_Domingo|America/Grenada","America/Santo_Domingo|America/Guadeloupe","America/Santo_Domingo|America/Kralendijk","America/Santo_Domingo|America/Lower_Princes","America/Santo_Domingo|America/Marigot","America/Santo_Domingo|America/Martinique","America/Santo_Domingo|America/Montserrat","America/Santo_Domingo|America/Port_of_Spain","America/Santo_Domingo|America/Puerto_Rico","America/Santo_Domingo|America/St_Barthelemy","America/Santo_Domingo|America/St_Kitts","America/Santo_Domingo|America/St_Lucia","America/Santo_Domingo|America/St_Thomas","America/Santo_Domingo|America/St_Vincent","America/Santo_Domingo|America/Tortola","America/Santo_Domingo|America/Virgin","America/Sao_Paulo|Brazil/East","America/St_Johns|Canada/Newfoundland","Antarctica/Palmer|America/Punta_Arenas","Asia/Baghdad|Antarctica/Syowa","Asia/Baghdad|Asia/Aden","Asia/Baghdad|Asia/Bahrain","Asia/Baghdad|Asia/Kuwait","Asia/Baghdad|Asia/Qatar","Asia/Baghdad|Asia/Riyadh","Asia/Baghdad|Etc/GMT-3","Asia/Baghdad|Europe/Minsk","Asia/Bangkok|Asia/Ho_Chi_Minh","Asia/Bangkok|Asia/Novokuznetsk","Asia/Bangkok|Asia/Phnom_Penh","Asia/Bangkok|Asia/Saigon","Asia/Bangkok|Asia/Vientiane","Asia/Bangkok|Etc/GMT-7","Asia/Bangkok|Indian/Christmas","Asia/Dhaka|Antarctica/Vostok","Asia/Dhaka|Asia/Almaty","Asia/Dhaka|Asia/Bishkek","Asia/Dhaka|Asia/Dacca","Asia/Dhaka|Asia/Kashgar","Asia/Dhaka|Asia/Qostanay","Asia/Dhaka|Asia/Thimbu","Asia/Dhaka|Asia/Thimphu","Asia/Dhaka|Asia/Urumqi","Asia/Dhaka|Etc/GMT-6","Asia/Dhaka|Indian/Chagos","Asia/Dili|Etc/GMT-9","Asia/Dili|Pacific/Palau","Asia/Dubai|Asia/Muscat","Asia/Dubai|Asia/Tbilisi","Asia/Dubai|Asia/Yerevan","Asia/Dubai|Etc/GMT-4","Asia/Dubai|Europe/Samara","Asia/Dubai|Indian/Mahe","Asia/Dubai|Indian/Mauritius","Asia/Dubai|Indian/Reunion","Asia/Gaza|Asia/Hebron","Asia/Hong_Kong|Hongkong","Asia/Jakarta|Asia/Pontianak","Asia/Jerusalem|Asia/Tel_Aviv","Asia/Jerusalem|Israel","Asia/Kamchatka|Asia/Anadyr","Asia/Kamchatka|Etc/GMT-12","Asia/Kamchatka|Kwajalein","Asia/Kamchatka|Pacific/Funafuti","Asia/Kamchatka|Pacific/Kwajalein","Asia/Kamchatka|Pacific/Majuro","Asia/Kamchatka|Pacific/Nauru","Asia/Kamchatka|Pacific/Tarawa","Asia/Kamchatka|Pacific/Wake","Asia/Kamchatka|Pacific/Wallis","Asia/Kathmandu|Asia/Katmandu","Asia/Kolkata|Asia/Calcutta","Asia/Kuala_Lumpur|Asia/Brunei","Asia/Kuala_Lumpur|Asia/Kuching","Asia/Kuala_Lumpur|Asia/Singapore","Asia/Kuala_Lumpur|Etc/GMT-8","Asia/Kuala_Lumpur|Singapore","Asia/Makassar|Asia/Ujung_Pandang","Asia/Rangoon|Asia/Yangon","Asia/Rangoon|Indian/Cocos","Asia/Seoul|ROK","Asia/Shanghai|Asia/Chongqing","Asia/Shanghai|Asia/Chungking","Asia/Shanghai|Asia/Harbin","Asia/Shanghai|Asia/Macao","Asia/Shanghai|Asia/Macau","Asia/Shanghai|Asia/Taipei","Asia/Shanghai|PRC","Asia/Shanghai|ROC","Asia/Tashkent|Antarctica/Mawson","Asia/Tashkent|Asia/Aqtau","Asia/Tashkent|Asia/Aqtobe","Asia/Tashkent|Asia/Ashgabat","Asia/Tashkent|Asia/Ashkhabad","Asia/Tashkent|Asia/Atyrau","Asia/Tashkent|Asia/Dushanbe","Asia/Tashkent|Asia/Oral","Asia/Tashkent|Asia/Samarkand","Asia/Tashkent|Etc/GMT-5","Asia/Tashkent|Indian/Kerguelen","Asia/Tashkent|Indian/Maldives","Asia/Tehran|Iran","Asia/Tokyo|Japan","Asia/Ulaanbaatar|Asia/Choibalsan","Asia/Ulaanbaatar|Asia/Ulan_Bator","Asia/Vladivostok|Asia/Ust-Nera","Asia/Yakutsk|Asia/Khandyga","Atlantic/Azores|America/Scoresbysund","Atlantic/Cape_Verde|Etc/GMT+1","Australia/Adelaide|Australia/Broken_Hill","Australia/Adelaide|Australia/South","Australia/Adelaide|Australia/Yancowinna","Australia/Brisbane|Australia/Lindeman","Australia/Brisbane|Australia/Queensland","Australia/Darwin|Australia/North","Australia/Lord_Howe|Australia/LHI","Australia/Perth|Australia/West","Australia/Sydney|Australia/ACT","Australia/Sydney|Australia/Canberra","Australia/Sydney|Australia/Currie","Australia/Sydney|Australia/Hobart","Australia/Sydney|Australia/Melbourne","Australia/Sydney|Australia/NSW","Australia/Sydney|Australia/Tasmania","Australia/Sydney|Australia/Victoria","Etc/UTC|Etc/UCT","Etc/UTC|Etc/Universal","Etc/UTC|Etc/Zulu","Etc/UTC|UCT","Etc/UTC|UTC","Etc/UTC|Universal","Etc/UTC|Zulu","Europe/Athens|Asia/Nicosia","Europe/Athens|EET","Europe/Athens|Europe/Bucharest","Europe/Athens|Europe/Helsinki","Europe/Athens|Europe/Kiev","Europe/Athens|Europe/Mariehamn","Europe/Athens|Europe/Nicosia","Europe/Athens|Europe/Riga","Europe/Athens|Europe/Sofia","Europe/Athens|Europe/Tallinn","Europe/Athens|Europe/Uzhgorod","Europe/Athens|Europe/Vilnius","Europe/Athens|Europe/Zaporozhye","Europe/Chisinau|Europe/Tiraspol","Europe/Dublin|Eire","Europe/Istanbul|Asia/Istanbul","Europe/Istanbul|Turkey","Europe/Lisbon|Atlantic/Canary","Europe/Lisbon|Atlantic/Faeroe","Europe/Lisbon|Atlantic/Faroe","Europe/Lisbon|Atlantic/Madeira","Europe/Lisbon|Portugal","Europe/Lisbon|WET","Europe/London|Europe/Belfast","Europe/London|Europe/Guernsey","Europe/London|Europe/Isle_of_Man","Europe/London|Europe/Jersey","Europe/London|GB","Europe/London|GB-Eire","Europe/Moscow|W-SU","Europe/Paris|Africa/Ceuta","Europe/Paris|Arctic/Longyearbyen","Europe/Paris|Atlantic/Jan_Mayen","Europe/Paris|CET","Europe/Paris|Europe/Amsterdam","Europe/Paris|Europe/Andorra","Europe/Paris|Europe/Belgrade","Europe/Paris|Europe/Berlin","Europe/Paris|Europe/Bratislava","Europe/Paris|Europe/Brussels","Europe/Paris|Europe/Budapest","Europe/Paris|Europe/Busingen","Europe/Paris|Europe/Copenhagen","Europe/Paris|Europe/Gibraltar","Europe/Paris|Europe/Ljubljana","Europe/Paris|Europe/Luxembourg","Europe/Paris|Europe/Madrid","Europe/Paris|Europe/Malta","Europe/Paris|Europe/Monaco","Europe/Paris|Europe/Oslo","Europe/Paris|Europe/Podgorica","Europe/Paris|Europe/Prague","Europe/Paris|Europe/Rome","Europe/Paris|Europe/San_Marino","Europe/Paris|Europe/Sarajevo","Europe/Paris|Europe/Skopje","Europe/Paris|Europe/Stockholm","Europe/Paris|Europe/Tirane","Europe/Paris|Europe/Vaduz","Europe/Paris|Europe/Vatican","Europe/Paris|Europe/Vienna","Europe/Paris|Europe/Warsaw","Europe/Paris|Europe/Zagreb","Europe/Paris|Europe/Zurich","Europe/Paris|Poland","Europe/Ulyanovsk|Europe/Astrakhan","Pacific/Auckland|Antarctica/McMurdo","Pacific/Auckland|Antarctica/South_Pole","Pacific/Auckland|NZ","Pacific/Chatham|NZ-CHAT","Pacific/Easter|Chile/EasterIsland","Pacific/Fakaofo|Etc/GMT-13","Pacific/Fakaofo|Pacific/Enderbury","Pacific/Galapagos|Etc/GMT+6","Pacific/Gambier|Etc/GMT+9","Pacific/Guadalcanal|Antarctica/Macquarie","Pacific/Guadalcanal|Etc/GMT-11","Pacific/Guadalcanal|Pacific/Efate","Pacific/Guadalcanal|Pacific/Kosrae","Pacific/Guadalcanal|Pacific/Noumea","Pacific/Guadalcanal|Pacific/Pohnpei","Pacific/Guadalcanal|Pacific/Ponape","Pacific/Guam|Pacific/Saipan","Pacific/Honolulu|HST","Pacific/Honolulu|Pacific/Johnston","Pacific/Honolulu|US/Hawaii","Pacific/Kiritimati|Etc/GMT-14","Pacific/Niue|Etc/GMT+11","Pacific/Pago_Pago|Pacific/Midway","Pacific/Pago_Pago|Pacific/Samoa","Pacific/Pago_Pago|US/Samoa","Pacific/Pitcairn|Etc/GMT+8","Pacific/Port_Moresby|Antarctica/DumontDUrville","Pacific/Port_Moresby|Etc/GMT-10","Pacific/Port_Moresby|Pacific/Chuuk","Pacific/Port_Moresby|Pacific/Truk","Pacific/Port_Moresby|Pacific/Yap","Pacific/Tahiti|Etc/GMT+10","Pacific/Tahiti|Pacific/Rarotonga"]}).zones,m=e.links,c={},i=u.map(function(a){var i=a.substr(0,a.indexOf("|"));return c[i]=a,i}),o=m.reduce(function(a,i){var e=i.split("|"),r=e[0];return a[e[1]]=r,a},{}),n={},a.convertDateToTime=function(a){var i=h(a),e=/\(([^)]+)\)$/.exec(a.toTimeString());return i.zone={abbreviation:e?e[1]:"???",offset:a.getTimezoneOffset()},z(i,a.getTime()),i},a.convertTimeToDate=function(a){var i=a.epoch;if(void 0!==i)return new Date(i);var e=(a.zone||{}).offset;if(void 0===e)return function(a){var i=a.year,e=a.month,r=a.day,A=a.hours,c=void 0===A?0:A,o=a.minutes,n=void 0===o?0:o,t=a.seconds,s=void 0===t?0:t,u=a.milliseconds;return new Date(i,e-1,r,c,n,s,void 0===u?0:u)}(a);var r=d(a);return new Date(r+6e4*e)},a.findTimeZone=function(a){var i=o[a]||a,e=n[i];if(!e){var r=c[i];if(!r)throw new Error('Unknown time zone "'+i+'".');e=n[i]=A(r)}return e},a.getUTCOffset=function(a,i){var e=T("number"==typeof a?a:a.getTime(),i);return{abbreviation:e.abbreviation,offset:e.offset}},a.getUnixTime=function(a,i){var e=a.zone,r=a.epoch;if(r){if(i)throw new Error("Both epoch and other time zone specified. Omit the other one.");return r}var A=d(a);if(e){if(i)throw new Error("Both own and other time zones specified. Omit the other one.")}else{if(!i)throw new Error("Missing other time zone.");e=T(A,i)}return A+6e4*e.offset},a.getZonedTime=function(a,i){var e="number"==typeof a,r=e?a:a.getTime(),A=T(r,i),c=A.abbreviation,o=A.offset;(e||o)&&(a=new Date(r-6e4*o));var n=E(a);return n.zone={abbreviation:c,offset:o},z(n,r),n},a.listTimeZones=function(){return i.slice()},a.setTimeZone=function(a,i,e){if(a instanceof Date)a=function(a,i){var e,r=(i||{}).useUTC;if(!0===r)e=E;else{if(!1!==r)throw new Error("Extract local or UTC date? Set useUTC option.");e=h}return e(a)}(a,e);else{var r=a,A=r.year,c=r.month,o=r.day,n=r.hours,t=r.minutes,s=r.seconds,u=void 0===s?0:s,m=r.milliseconds;a={year:A,month:c,day:o,hours:n,minutes:t,seconds:u,milliseconds:void 0===m?0:m}}var l=d(a),f=new Date(l);a.dayOfWeek=f.getUTCDay();var p=T(l,i),M=p.abbreviation,b=p.offset;return a.zone={abbreviation:M,offset:b},z(a,l+6e4*b),a},Object.defineProperty(a,"__esModule",{value:!0})});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.dayjs_plugin_timezone=e()}(this,function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,u=o().utcOffset(),a=function(t,n,i){void 0===i&&(i={});var o=new Date(t);return function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i).formatToParts(o)},f=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],v=24===d?0:d,h=r[0]+"-"+r[1]+"-"+r[2]+" "+v+":"+r[4]+":"+r[5]+":000",l=+e;return(o.utc(h).valueOf()-(l-=l%1e3))/6e4},s=i.prototype;s.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate().toLocaleString("en-US",{timeZone:t}),a=Math.round((this.toDate()-new Date(i))/1e3/60),f=o(i).$set("millisecond",this.$ms).utcOffset(u-a,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},s.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find(function(t){return"timezonename"===t.type.toLowerCase()});return n&&n.value},o.tz=function(t,e,n){var i=n&&e,u=n||e||r,a=f(+o(),u);if("string"!=typeof t)return o(t).tz(u);var s=function(t,e,n){var i=t-60*e*1e3,o=f(i,n);if(e===o)return[i,e];var r=f(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),a,u),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=u,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}});

!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):t.dayjs_plugin_utc=i()}(this,function(){"use strict";return function(t,i,e){var s=i.prototype;e.utc=function(t){return new i({date:t,utc:!0,args:arguments})},s.utc=function(t){var i=e(this.toDate(),{locale:this.$L,utc:!0});return t?i.add(this.utcOffset(),"minute"):i},s.local=function(){return e(this.toDate(),{locale:this.$L,utc:!1})};var f=s.parse;s.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),f.call(this,t)};var n=s.init;s.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds()}else n.call(this)};var u=s.utcOffset;s.utcOffset=function(t,i){var e=this.$utils().u;if(e(t))return this.$u?0:e(this.$offset)?u.call(this):this.$offset;var s=Math.abs(t)<=16?60*t:t,f=this;if(i)return f.$offset=s,f.$u=0===t,f;if(0!==t){var n=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(f=this.local().add(s+n,"minute")).$offset=s,f.$x.$localOffset=n}else f=this.utc();return f};var o=s.format;s.format=function(t){var i=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return o.call(this,i)},s.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||(new Date).getTimezoneOffset());return this.$d.valueOf()-6e4*t},s.isUTC=function(){return!!this.$u},s.toISOString=function(){return this.toDate().toISOString()},s.toString=function(){return this.toDate().toUTCString()};var r=s.toDate;s.toDate=function(t){return"s"===t&&this.$offset?e(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():r.call(this)};var a=s.diff;s.diff=function(t,i,s){if(this.$u===t.$u)return a.call(this,t,i,s);var f=this.local(),n=e(t).local();return a.call(f,n,i,s)}}});


/*!
 * JavaScript Cookie v2.1.4
 * https://github.com/js-cookie/js-cookie
 *
 * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
 * Released under the MIT license
 */
; (function (factory) {
    var registeredInModuleLoader = false;
    if (typeof define === 'function' && define.amd) {
        define(factory);
        registeredInModuleLoader = true;
    }
    if (typeof exports === 'object') {
        module.exports = factory();
        registeredInModuleLoader = true;
    }
    if (!registeredInModuleLoader) {
        var OldCookies = window.Cookies;
        var api = window.Cookies = factory();
        api.noConflict = function () {
            window.Cookies = OldCookies;
            return api;
        };
    }
}(function () {
    function extend() {
        var i = 0;
        var result = {};
        for (; i < arguments.length; i++) {
            var attributes = arguments[i];
            for (var key in attributes) {
                result[key] = attributes[key];
            }
        }
        return result;
    }

    function init(converter) {
        function api(key, value, attributes) {
            var result;
            if (typeof document === 'undefined') {
                return;
            }

            // Write

            if (arguments.length > 1) {
                attributes = extend({
                    path: '/'
                }, api.defaults, attributes);

                if (typeof attributes.expires === 'number') {
                    var expires = new Date();
                    expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
                    attributes.expires = expires;
                }

                // We're using "expires" because "max-age" is not supported by IE
                attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';

                try {
                    result = JSON.stringify(value);
                    if (/^[\{\[]/.test(result)) {
                        value = result;
                    }
                } catch (e) { }

                if (!converter.write) {
                    value = encodeURIComponent(String(value))
                        .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
                } else {
                    value = converter.write(value, key);
                }

                key = encodeURIComponent(String(key));
                key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
                key = key.replace(/[\(\)]/g, escape);

                var stringifiedAttributes = '';

                for (var attributeName in attributes) {
                    if (!attributes[attributeName]) {
                        continue;
                    }
                    stringifiedAttributes += '; ' + attributeName;
                    if (attributes[attributeName] === true) {
                        continue;
                    }
                    stringifiedAttributes += '=' + attributes[attributeName];
                }
                return (document.cookie = key + '=' + value + stringifiedAttributes);
            }

            // Read

            if (!key) {
                result = {};
            }

            // To prevent the for loop in the first place assign an empty array
            // in case there are no cookies at all. Also prevents odd result when
            // calling "get()"
            var cookies = document.cookie ? document.cookie.split('; ') : [];
            var rdecode = /(%[0-9A-Z]{2})+/g;
            var i = 0;

            for (; i < cookies.length; i++) {
                var parts = cookies[i].split('=');
                var cookie = parts.slice(1).join('=');

                if (cookie.charAt(0) === '"') {
                    cookie = cookie.slice(1, -1);
                }

                try {
                    var name = parts[0].replace(rdecode, decodeURIComponent);
                    cookie = converter.read ?
                        converter.read(cookie, name) : converter(cookie, name) ||
                        cookie.replace(rdecode, decodeURIComponent);

                    if (this.json) {
                        try {
                            cookie = JSON.parse(cookie);
                        } catch (e) { }
                    }

                    if (key === name) {
                        result = cookie;
                        break;
                    }

                    if (!key) {
                        result[name] = cookie;
                    }
                } catch (e) { }
            }

            return result;
        }

        api.set = api;
        api.get = function (key) {
            return api.call(api, key);
        };
        api.getJSON = function () {
            return api.apply({
                json: true
            }, [].slice.call(arguments));
        };
        api.defaults = {};

        api.remove = function (key, attributes) {
            api(key, '', extend(attributes, {
                expires: -1
            }));
        };

        api.withConverter = init;

        return api;
    }

    return init(function () { });
}));
/**
*  Ajax Autocomplete for jQuery, version 1.2.24
*  (c) 2015 Tomas Kirda
*
*  Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
*  For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
*/

/*jslint  browser: true, white: true, plusplus: true, vars: true */
/*global define, window, document, jQuery, exports, require */

// Expose plugin as an AMD module if AMD loader is present:
(function (factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if (typeof exports === 'object' && typeof require === 'function') {
        // Browserify
        factory(require('jquery'));
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function ($) {
    'use strict';

    var
        utils = (function () {
            return {
                escapeRegExChars: function (value) {
                    return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
                },
                createNode: function (containerClass) {
                    var div = document.createElement('div');
                    div.className = containerClass;
                    div.style.position = 'absolute';
                    div.style.display = 'none';
                    return div;
                }
            };
        }()),

        keys = {
            ESC: 27,
            TAB: 9,
            RETURN: 13,
            LEFT: 37,
            UP: 38,
            RIGHT: 39,
            DOWN: 40
        };

    // This is an auxiliary woodmart function to replace the outdated jquery method.
    function wdTrim(data) {
        if ( null == data ) {
            return '';
        } else if ( 'string' == typeof data ) {
            return data.trim();
        } else {
            return (data + '').replace( '/^[\\s\uFEFF\xA0]+|[\\s\uFEFF\xA0]+$/g', '' );
        }
    }

    function Autocomplete(el, options) {
        var noop = function () { },
            that = this,
            defaults = {
                ajaxSettings: {},
                autoSelectFirst: false,
                appendTo: document.body,
                serviceUrl: null,
                lookup: null,
                onSelect: null,
                width: 'auto',
                minChars: 1,
                maxHeight: 300,
                deferRequestBy: 0,
                params: {},
                formatResult: Autocomplete.formatResult,
                delimiter: null,
                zIndex: 9999,
                type: 'GET',
                noCache: false,
                onSearchStart: noop,
                onSearchComplete: noop,
                onSearchError: noop,
                preserveInput: false,
                containerClass: 'wd-search-suggestions',
                tabDisabled: false,
                dataType: 'text',
                currentRequest: null,
                triggerSelectOnValidInput: true,
                preventBadQueries: true,
                lookupFilter: function (suggestion, originalQuery, queryLowerCase) {
                    return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
                },
                paramName: 'query',
                transformResult: function (response) {
                    return typeof response === 'string' ? JSON.parse(response) : response;
                },
                showNoSuggestionNotice: false,
                noSuggestionNotice: 'No results',
                orientation: 'bottom',
                forceFixPosition: false
            };

        // Shared variables:
        that.element = el;
        that.el = $(el);
        that.suggestions = [];
        that.badQueries = [];
        that.selectedIndex = -1;
        that.currentValue = that.element.value;
        that.intervalId = 0;
        that.cachedResponse = {};
        that.onChangeInterval = null;
        that.onChange = null;
        that.isLocal = false;
        that.suggestionsContainer = null;
        that.noSuggestionsContainer = null;
        that.options = $.extend({}, defaults, options);
        that.classes = {
            selected: 'wd-active',
            suggestion: 'wd-suggestion'
        };
        that.hint = null;
        that.hintValue = '';
        that.selection = null;

        // Initialize and set options:
        that.initialize();
        that.setOptions(options);
    }

    Autocomplete.utils = utils;

    $.Autocomplete = Autocomplete;

    Autocomplete.formatResult = function (suggestion, currentValue) {
        var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
        
        return suggestion.value
            .replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/&lt;(\/?strong)&gt;/g, '<$1>');
    };

    Autocomplete.prototype = {

        killerFn: null,

        initialize: function () {
            var that = this,
                suggestionSelector = `.${that.classes.suggestion}`,
                selected = that.classes.selected,
                options = that.options,
                container;

            // Remove autocomplete attribute to prevent native suggestions:
            that.element.setAttribute('autocomplete', 'off');

            that.killerFn = function (e) {
                if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
                    that.killSuggestions(e);
                    that.disableKillerFn();
                }
            };

            // html() deals with many types: htmlString or Element or Array or jQuery
            that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
                                          .html(this.options.noSuggestionNotice).get(0);

            that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);

            container = $(that.suggestionsContainer);

            container.appendTo(options.appendTo);

            // Only set width if it was provided:
            if (options.width !== 'auto') {
                container.width(options.width);
            }

            // Listen for mouse over event on suggestions list:
            container.on('mouseover.autocomplete', suggestionSelector, function (e) {
                if ($(this).hasClass('wd-not-found')) {
                    e.preventDefault();
                    return false;
                }

                that.activate($(this).data('index'));
            });

            // Deselect active element when mouse leaves suggestions container:
            container.on('mouseout.autocomplete', function () {
                that.selectedIndex = -1;
                container.find('.' + selected).removeClass(selected);
            });

            // Listen for click event on suggestions list:
            container.on('click.autocomplete', suggestionSelector, function (e) {
                if ($(this).hasClass('wd-not-found') || $(this).hasClass('wd-search-title')) {
                    e.preventDefault();
                    return false;
                }

                var doNothing = $(this).find('> a').length > 0;

                that.select($(this).data('index'), doNothing);
            });

            that.fixPositionCapture = function () {
                if (that.visible) {
                    that.fixPosition();
                }
            };

            $(window).on('resize.autocomplete', that.fixPositionCapture);

            that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
            that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
            that.el.on('blur.autocomplete', function () { that.onBlur(); });
            that.el.on('focus.autocomplete', function () { that.onFocus(); });
            that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
            that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); });

            var clearBtn = that.el.parent().find('.wd-clear-search');

            if (clearBtn) {
                clearBtn.on('click', function (e) { that.onClearSearch(e); });
            }
        },

        onClearSearch: function (e) {
            var that = this;

            if (e.target.classList.contains('wd-clear-search')) {
                e.target.classList.add('wd-hide');
            }

            that.clear();
            that.killSuggestions(e);
            that.el.trigger('focus');
        },

        onFocus: function () {
            var that = this;
            that.fixPosition();
            if (that.options.minChars === 0 && that.el.val().length === 0) {
                that.onValueChange();
            }
        },

        onBlur: function () {
            this.enableKillerFn();
        },

        abortAjax: function () {
            var that = this;
            if (that.currentRequest) {
                that.currentRequest.abort();
                that.currentRequest = null;
            }
        },

        setOptions: function (suppliedOptions) {
            var that = this,
                options = that.options;

            $.extend(options, suppliedOptions);

            that.isLocal = Array.isArray(options.lookup);

            if (that.isLocal) {
                options.lookup = that.verifySuggestionsFormat(options.lookup);
            }

            options.orientation = that.validateOrientation(options.orientation, 'bottom');

            // Adjust height, width and z-index:
            $(that.suggestionsContainer).css({
                'max-height': options.maxHeight + 'px',
                'width': options.width + 'px',
                'z-index': options.zIndex
            });
        },


        clearCache: function () {
            this.cachedResponse = {};
            this.badQueries = [];
        },

        clear: function () {
            this.clearCache();
            this.currentValue = '';
            this.suggestions = [];
        },

        disable: function () {
            var that = this;
            that.disabled = true;
            clearInterval(that.onChangeInterval);
            that.abortAjax();
        },

        enable: function () {
            this.disabled = false;
        },

        fixPosition: function () {
            // Use only when container has already its content

            var that = this,
                $container = $(that.suggestionsContainer),
                containerParent = $container.parent().get(0);
            // Fix position automatically when appended to body.
            // In other cases force parameter must be given.
            if (containerParent !== document.body && !that.options.forceFixPosition) {
                return;
            }

            // Choose orientation
            var orientation = that.options.orientation,
                containerHeight = $container.outerHeight(),
                height = that.el.outerHeight(),
                offset = that.el.offset(),
                styles = { 'top': offset.top, 'left': offset.left };

            if (orientation === 'auto') {
                var viewPortHeight = $(window).height(),
                    scrollTop = $(window).scrollTop(),
                    topOverflow = -scrollTop + offset.top - containerHeight,
                    bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);

                orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom';
            }

            if (orientation === 'top') {
                styles.top += -containerHeight;
            } else {
                styles.top += height;
            }

            // If container is not positioned to body,
            // correct its position using offset parent offset
            if(containerParent !== document.body) {
                var opacity = $container.css('opacity'),
                    parentOffsetDiff;

                    if (!that.visible){
                        $container.css('opacity', 0).show();
                    }

                parentOffsetDiff = $container.offsetParent().offset();
                styles.top -= parentOffsetDiff.top;
                styles.left -= parentOffsetDiff.left;

                if (!that.visible){
                    $container.css('opacity', opacity).hide();
                }
            }

            // -2px to account for suggestions border.
            if (that.options.width === 'auto') {
                styles.width = (that.el.outerWidth() - 2) + 'px';
            }

            $container.css(styles);
        },

        enableKillerFn: function () {
            var that = this;
            $(document).on('click.autocomplete', that.killerFn);
        },

        disableKillerFn: function () {
            var that = this;
            $(document).off('click.autocomplete', that.killerFn);
        },

        killSuggestions: function (e) {
            var that = this;
            var isClearBtn = $(e.target).hasClass('wd-clear-search');

            that.stopKillSuggestions();

            that.intervalId = window.setInterval(function () {
                if (that.visible) {
                    that.el.val(that.currentValue);
                    that.hide(false, isClearBtn);
                }
                
                that.stopKillSuggestions();
            }, 50);
        },

        stopKillSuggestions: function () {
            window.clearInterval(this.intervalId);
        },

        isCursorAtEnd: function () {
            var that = this,
                valLength = that.el.val().length,
                selectionStart = that.element.selectionStart,
                range;

            if (typeof selectionStart === 'number') {
                return selectionStart === valLength;
            }
            if (document.selection) {
                range = document.selection.createRange();
                range.moveStart('character', -valLength);
                return valLength === range.text.length;
            }
            return true;
        },

        onKeyPress: function (e) {
            var that = this;

            // If suggestions are hidden and user presses arrow down, display suggestions:
            if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
                that.suggest();
                return;
            }

            if (that.disabled || !that.visible) {
                return;
            }

            switch (e.which) {
                case keys.ESC:
                    that.el.val(that.currentValue);
                    that.hide();
                    break;
                case keys.RIGHT:
                    if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
                        that.selectHint();
                        break;
                    }
                    return;
                case keys.TAB:
                    if (that.hint && that.options.onHint) {
                        that.selectHint();
                        return;
                    }
                    if (that.selectedIndex === -1) {
                        that.hide();
                        return;
                    }
                    that.select(that.selectedIndex);
                    if (that.options.tabDisabled === false) {
                        return;
                    }
                    break;
                case keys.RETURN:
                    if (-1 === that.selectedIndex) {
                        that.hide(true);
                        return;
                    }

                    that.select(that.selectedIndex, true);
                    break;
                case keys.UP:
                    that.moveUp();
                    break;
                case keys.DOWN:
                    that.moveDown();
                    break;
                default:
                    return;
            }

            // Cancel event if function did not return:
            e.stopImmediatePropagation();
            e.preventDefault();
        },

        onKeyUp: function (e) {
            var that = this;

            if (that.disabled) {
                return;
            }

            switch (e.which) {
                case keys.UP:
                case keys.DOWN:
                    return;
            }

            clearInterval(that.onChangeInterval);

            if (that.currentValue !== that.el.val()) {
                that.findBestHint();
                if (that.options.deferRequestBy > 0) {
                    // Defer lookup in case when value changes very quickly:
                    that.onChangeInterval = setInterval(function () {
                        that.onValueChange();
                    }, that.options.deferRequestBy);
                } else {
                    that.onValueChange();
                }
            }
        },

        onValueChange: function () {
            var that = this,
                options = that.options,
                value = that.el.val(),
                query = that.getQuery(value);

            if (that.selection && that.currentValue !== query) {
                that.selection = null;
                (options.onInvalidateSelection || $.noop).call(that.element);
            }

            clearInterval(that.onChangeInterval);
            that.currentValue = value;
            that.selectedIndex = -1;

            // Check existing suggestion for the match before proceeding:
            if (options.triggerSelectOnValidInput && that.isExactMatch(query)) {
                that.select(0);
                return;
            }

            if (query.length < options.minChars) {
                that.hide();
            } else {
                that.getSuggestions(query);
            }
        },

        isExactMatch: function (query) {
            var suggestions = this.suggestions;

            return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase());
        },

        getQuery: function (value) {
            var delimiter = this.options.delimiter,
                parts;

            if (!delimiter) {
                return value;
            }
            parts = value.split(delimiter);
            return wdTrim(parts[parts.length - 1]);
        },

        getSuggestionsLocal: function (query) {
            var that = this,
                options = that.options,
                queryLowerCase = query.toLowerCase(),
                filter = options.lookupFilter,
                limit = parseInt(options.lookupLimit, 10),
                data;

            data = {
                suggestions: $.grep(options.lookup, function (suggestion) {
                    return filter(suggestion, query, queryLowerCase);
                })
            };

            if (limit && data.suggestions.length > limit) {
                data.suggestions = data.suggestions.slice(0, limit);
            }

            return data;
        },

        getSuggestions: function (q) {
            var response,
                that = this,
                options = that.options,
                serviceUrl = options.serviceUrl,
                params,
                cacheKey,
                ajaxSettings;

            options.params[options.paramName] = q;
            params = options.ignoreParams ? null : options.params;

            if (options.onSearchStart.call(that.element, options.params) === false) {
                return;
            }

            if (typeof options.lookup === "function"){
                options.lookup(q, function (data) {
                    that.suggestions = data.suggestions;
                    that.suggest();
                    options.onSearchComplete.call(that.element, q, data.suggestions);
                });
                return;
            }

            if (that.isLocal) {
                response = that.getSuggestionsLocal(q);
            } else {
                if (typeof serviceUrl === "function") {
                    serviceUrl = serviceUrl.call(that.element, q);
                }
                cacheKey = serviceUrl + '?' + $.param(params || {});
                response = that.cachedResponse[cacheKey];
            }

            if (response && Array.isArray(response.suggestions)) {
                that.suggestions = response.suggestions;
                that.suggest();
                options.onSearchComplete.call(that.element, q, response.suggestions);
            } else if (!that.isBadQuery(q)) {
                that.abortAjax();

                ajaxSettings = {
                    url: serviceUrl,
                    data: params,
                    type: options.type,
                    dataType: options.dataType
                };

                $.extend(ajaxSettings, options.ajaxSettings);

                that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
                    var result;
                    that.currentRequest = null;
                    result = options.transformResult(data, q);
                    that.processResponse(result, q, cacheKey);
                    options.onSearchComplete.call(that.element, q, result.suggestions);
                }).fail(function (jqXHR, textStatus, errorThrown) {
                    options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown);
                });
            } else {
                options.onSearchComplete.call(that.element, q, []);
            }
        },

        isBadQuery: function (q) {
            if (!this.options.preventBadQueries){
                return false;
            }

            var badQueries = this.badQueries,
                i = badQueries.length;

            while (i--) {
                if (q.indexOf(badQueries[i]) === 0) {
                    return true;
                }
            }

            return false;
        },

        hide: function (doNothing = false, isClearBtn = false) {
            if (doNothing) {
                return;
            }

            var that = this;
            var container = $(that.suggestionsContainer);

            if (typeof that.options.onHide === "function" && that.visible) {
                that.options.onHide.call(that.element, container, isClearBtn);
            }

            that.visible = false;
            that.selectedIndex = -1;
            clearInterval(that.onChangeInterval);
            $(that.suggestionsContainer).hide();
            that.signalHint(null);
        },

        suggest: function () {
            if (this.suggestions.length === 0) {
                if (this.options.showNoSuggestionNotice) {
                    this.noSuggestions();
                } else {
                    this.hide();
                }
                return;
            }

            var that = this,
                options = that.options,
                groupBy = options.groupBy,
                formatResult = options.formatResult,
                value = that.getQuery(that.currentValue),
                className = that.classes.suggestion,
                classSelected = that.classes.selected,
                container = $(that.suggestionsContainer),
                noSuggestionsContainer = $(that.noSuggestionsContainer),
                beforeRender = options.beforeRender,
                html = '',
                category,
                formatGroup = function (suggestion, index) {
                        var currentCategory = suggestion.data[groupBy];

                        if (category === currentCategory){
                            return '';
                        }

                        category = currentCategory;

                        return '<div class="autocomplete-group"><strong>' + category + '</strong></div>';
                    };

            if (options.triggerSelectOnValidInput && that.isExactMatch(value)) {
                that.select(0);
                return;
            }

            var getSuggestionsHtml = function(suggestions, html = '') {
                $.each(suggestions, function (i, suggestion) {
                    if (groupBy){
                        html += formatGroup(suggestion, value, i);
                    }
    
                    var itemClassName = className;
    
                    if (suggestion.item_classes) {
                        itemClassName += ' ' + suggestion.item_classes;
                    }

                    html += '<div class="' + itemClassName + '" data-index="' + i + '">' + formatResult(suggestion, value) + '</div>';
                });

                return html;
            }

            var indexCounter = 0;
            var grupedSuggestions = that.suggestions.reduce((acc, suggestion) => {
                const group = suggestion.group || 'default';
                if (!acc[group]) {
                    acc[group] = {};
                }
                acc[group][indexCounter] = suggestion;
                indexCounter++;
                return acc;
            }, {});

            if (grupedSuggestions) {
                $.each(grupedSuggestions, function (group, suggestions) {
                    $.each(suggestions, function(i, suggestion) {
                        if (! suggestion) {
                            return;
                        }

                        if (suggestion.divider) {
                            html += '<div class="wd-search-title title" data-index="' + i + '">' + suggestion.divider + '</div>';

                            delete suggestions[i];
                        }
                    });

                    var groupClassName = `wd-suggestions-group wd-type-${group}`;
        
                    html += '<div class="' + groupClassName + '">';

                    html = getSuggestionsHtml(suggestions, html);
    
                    html += '</div>';
                });
            } else {
                html = getSuggestionsHtml(that.suggestions);
            }

            this.adjustContainerWidth();

            noSuggestionsContainer.detach();
            container.html(html);

            if (typeof beforeRender === "function") {
                beforeRender.call(that.element, container);
            }

            that.fixPosition();
            container.show();

            // Select first value by default:
            if (options.autoSelectFirst) {
                that.selectedIndex = 0;
                container.scrollTop(0);
                container.children('.' + className).first().addClass(classSelected);
            }

            that.visible = true;
            that.findBestHint();
        },

        noSuggestions: function() {
             var that = this,
                 container = $(that.suggestionsContainer),
                 noSuggestionsContainer = $(that.noSuggestionsContainer);

            this.adjustContainerWidth();

            // Some explicit steps. Be careful here as it easy to get
            // noSuggestionsContainer removed from DOM if not detached properly.
            noSuggestionsContainer.detach();
            container.empty(); // clean suggestions if any
            container.append(noSuggestionsContainer);

            that.fixPosition();

            container.show();
            that.visible = true;
        },

        adjustContainerWidth: function() {
            var that = this,
                options = that.options,
                width,
                container = $(that.suggestionsContainer);

            // If width is auto, adjust width before displaying suggestions,
            // because if instance was created before input had width, it will be zero.
            // Also it adjusts if input width has changed.
            // -2px to account for suggestions border.
            if (options.width === 'auto') {
                width = that.el.outerWidth() - 2;
                container.width(width > 0 ? width : 300);
            }
        },

        findBestHint: function () {
            var that = this,
                value = that.el.val().toLowerCase(),
                bestMatch = null;

            if (!value) {
                return;
            }

            $.each(that.suggestions, function (i, suggestion) {
                var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
                if (foundMatch) {
                    bestMatch = suggestion;
                }
                return !foundMatch;
            });

            that.signalHint(bestMatch);
        },

        signalHint: function (suggestion) {
            var hintValue = '',
                that = this;
            if (suggestion) {
                hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
            }
            if (that.hintValue !== hintValue) {
                that.hintValue = hintValue;
                that.hint = suggestion;
                (this.options.onHint || $.noop)(hintValue);
            }
        },

        verifySuggestionsFormat: function (suggestions) {
            // If suggestions is string array, convert them to supported format:
            if (suggestions.length && typeof suggestions[0] === 'string') {
                return $.map(suggestions, function (value) {
                    return { value: value, data: null };
                });
            }

            return suggestions;
        },

        validateOrientation: function(orientation, fallback) {
            orientation = $.trim(orientation || '').toLowerCase();

            if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
                orientation = fallback;
            }

            return orientation;
        },

        processResponse: function (result, originalQuery, cacheKey) {
            var that = this,
                options = that.options;

            result.suggestions = that.verifySuggestionsFormat(result.suggestions);

            // Cache results if cache is not disabled:
            if (!options.noCache) {
                that.cachedResponse[cacheKey] = result;
                if (options.preventBadQueries && result.suggestions.length === 0) {
                    that.badQueries.push(originalQuery);
                }
            }

            // Return if originalQuery is not matching current query:
            if (originalQuery !== that.getQuery(that.currentValue)) {
                return;
            }

            that.suggestions = result.suggestions;
            that.suggest();
        },

        activate: function (index) {
            var that = this;
            var selected = that.classes.selected;
            var container = $(that.suggestionsContainer);
            var activeItem = container.find(`.${that.classes.suggestion}[data-index="${index}"]`);

            container.find('.' + selected).removeClass(selected);

            that.selectedIndex = index;

            if (that.selectedIndex !== -1 && activeItem) {
                $(activeItem).addClass(selected);

                return activeItem;
            }

            return null;
        },

        selectHint: function () {
            var that = this,
                i = $.inArray(that.hint, that.suggestions);

            that.select(i);
        },

        select: function (i, doNothing = false) {            
            if (doNothing) {
                return;
            }

            var that = this;
            that.hide();
            that.onSelect(i);
        },

        moveUp: function () {
            var that = this;

            if (that.selectedIndex === -1) {
                return;
            }

            if (that.selectedIndex === 0) {
                $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
                that.selectedIndex = -1;
                that.el.val(that.currentValue);
                that.findBestHint();
                return;
            }

            that.adjustScroll(that.selectedIndex - 1);
        },

        moveDown: function () {
            var that = this;

            if (that.selectedIndex === (that.suggestions.length - 1)) {
                return;
            }

            that.adjustScroll(that.selectedIndex + 1);
        },

        adjustScroll: function (index) {
            var that = this,
                activeItem = that.activate(index);

            if (!activeItem) {
                return;
            }

            var offsetTop,
                upperBound,
                lowerBound,
                heightDelta = $(activeItem).outerHeight();

            offsetTop = activeItem.offsetTop;
            upperBound = $(that.suggestionsContainer).scrollTop();
            lowerBound = upperBound + that.options.maxHeight - heightDelta;

            if (offsetTop < upperBound) {
                $(that.suggestionsContainer).scrollTop(offsetTop);
            } else if (offsetTop > lowerBound) {
                $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
            }

            if (!that.options.preserveInput) {
                that.el.val(that.getValue(that.suggestions[index].value));
            }
            that.signalHint(null);
        },

        onSelect: function (index) {
            var that = this,
                onSelectCallback = that.options.onSelect,
                suggestion = that.suggestions[index];

            that.currentValue = that.getValue(suggestion.value);

            if (that.currentValue !== that.el.val() && !that.options.preserveInput) {
                that.el.val(that.currentValue);
            }

            that.signalHint(null);
            that.suggestions = [];
            that.selection = suggestion;

            if (typeof onSelectCallback === "function") {
                onSelectCallback.call(that.element, suggestion);
            }
        },

        getValue: function (value) {
            var that = this,
                delimiter = that.options.delimiter,
                currentValue,
                parts;

            if (!delimiter) {
                return value;
            }

            currentValue = that.currentValue;
            parts = currentValue.split(delimiter);

            if (parts.length === 1) {
                return value;
            }

            return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
        },

        dispose: function () {
            var that = this;
            that.el.off('.autocomplete').removeData('autocomplete');
            that.disableKillerFn();
            $(window).off('resize.autocomplete', that.fixPositionCapture);
            $(that.suggestionsContainer).remove();
        }
    };

    // Create chainable jQuery plugin:
    $.fn.devbridgeAutocomplete = function (options, args) {
        var dataKey = 'autocomplete';
        // If function invoked without argument return
        // instance of the first matched element:
        if (arguments.length === 0) {
            return this.first().data(dataKey);
        }

        return this.each(function () {
            var inputElement = $(this),
                instance = inputElement.data(dataKey);

            if (typeof options === 'string') {
                if (instance && typeof instance[options] === 'function') {
                    instance[options](args);
                }
            } else {
                // If instance already exists, destroy it:
                if (instance && instance.dispose) {
                    instance.dispose();
                }
                instance = new Autocomplete(this, options);
                inputElement.data(dataKey, instance);
            }
        });
    };
}));

