/* InstantClick 3.1.0 | (C) 2014-2017 Alexandre Dieulot | http://instantclick.io/license */

var instantclick
  , InstantClick = instantclick = function(document, location, $userAgent) {
  // Internal variables
  var $currentLocationWithoutHash
    , $urlToPreload
    , $preloadTimer
    , $lastTouchTimestamp
    , $hasBeenInitialized
    , $touchEndedWithoutClickTimer
    , $lastUsedTimeoutId = 0

  // Preloading-related variables
    , $history = {}
    , $xhr
    , $url = false
    , $title = false
    , $isContentTypeNotHTML
    , $areTrackedElementsDifferent
    , $body = false
    , $lastDisplayTimestamp = 0
    , $isPreloading = false
    , $isWaitingForCompletion = false
    , $gotANetworkError = false
    , $trackedElementsData = []

  // Variables defined by public functions
    , $preloadOnMousedown
    , $delayBeforePreload = 65
    , $eventsCallbacks = {
        preload: [],
        receive: [],
        wait: [],
        change: [],
        restore: [],
        exit: []
      }
    , $timers = {}
    , $currentPageXhrs = []
    , $windowEventListeners = {}
    , $delegatedEvents = {}


  ////////// POLYFILL //////////


  // Needed for `addEvent`
  if (!Element.prototype.matches) {
    Element.prototype.matches =
      Element.prototype.webkitMatchesSelector ||
      Element.prototype.msMatchesSelector ||
      function (selector) {
        var matches = document.querySelectorAll(selector)
        for (var i = 0; i < matches.length; i++) {
          if (matches[i] == this) {
            return true
          }
        }
        return false
      }
  }


  ////////// HELPERS //////////


  function removeHash(url) {
    var index = url.indexOf('#')
    if (index == -1) {
      return url
    }
    return url.substr(0, index)
  }

  function getParentLinkElement(element) {
    while (element && element.nodeName != 'A') {
      element = element.parentNode
    }
    // `element` will be null if no link element is found
    return element
  }

  function isBlacklisted(element) {
    do {
      if (!element.hasAttribute) { // Parent of <html>
        break
      }
      if (element.hasAttribute('data-instant')) {
        return false
      }
      if (element.hasAttribute('data-no-instant')) {
        return true
      }
      /**
       * Added by LiteSpeed to avoid clicking wrong link
       */
      if (element.getAttribute('rel') == 'nofollow' ) {
        return true
      }
    }
    while (element = element.parentNode)
    return false
  }

  function isPreloadable(linkElement) {
    var domain = location.protocol + '//' + location.host

    if (linkElement.target // target="_blank" etc.
        || linkElement.hasAttribute('download')
        || linkElement.href.indexOf(domain + '/') != 0 // Another domain, or no href attribute
        || (linkElement.href.indexOf('#') > -1
            && removeHash(linkElement.href) == $currentLocationWithoutHash) // Anchor
        || isBlacklisted(linkElement)
       ) {
      return false
    }
    return true
  }

  function triggerPageEvent(eventType) {
    var argumentsToApply = Array.prototype.slice.call(arguments, 1)
      , returnValue = false
    for (var i = 0; i < $eventsCallbacks[eventType].length; i++) {
      if (eventType == 'receive') {
        var altered = $eventsCallbacks[eventType][i].apply(window, argumentsToApply)
        if (altered) {
          // Update arguments for the next iteration of the loop.
          if ('body' in altered) {
            argumentsToApply[1] = altered.body
          }
          if ('title' in altered) {
            argumentsToApply[2] = altered.title
          }

          returnValue = altered
        }
      }
      else {
        $eventsCallbacks[eventType][i].apply(window, argumentsToApply)
      }
    }
    return returnValue
  }

  function changePage(title, body, urlToPush, scrollPosition) {
    abortCurrentPageXhrs()

    document.documentElement.replaceChild(body, document.body)
    // We cannot just use `document.body = doc.body`, it causes Safari (tested
    // 5.1, 6.0 and Mobile 7.0) to execute script tags directly.

    document.title = title

    if (urlToPush) {
      addOrRemoveWindowEventListeners('remove')
      if (urlToPush != location.href) {
        history.pushState(null, null, urlToPush)

        if ($userAgent.indexOf(' CriOS/') > -1) {
          // Chrome for iOS:
          //
          // 1. Removes title in tab on pushState, so it needs to be set after.
          //
          // 2. Will not set the title if it's identical after trimming, so we
          //    add a non-breaking space.
          if (document.title == title) {
            document.title = title + String.fromCharCode(160)
          }
          else {
            document.title = title
          }
        }
      }

      var hashIndex = urlToPush.indexOf('#')
        , offsetElement = hashIndex > -1
                     && document.getElementById(urlToPush.substr(hashIndex + 1))
        , offset = 0

      if (offsetElement) {
        while (offsetElement.offsetParent) {
          offset += offsetElement.offsetTop

          offsetElement = offsetElement.offsetParent
        }
      }
      if ('requestAnimationFrame' in window) {
        // Safari on macOS doesn't immediately visually change the page on
        // `document.documentElement.replaceChild`, so if `scrollTo` is called
        // without `requestAnimationFrame` it often scrolls before the page
        // is displayed.
        requestAnimationFrame(function() {
          scrollTo(0, offset)
        })
      }
      else {
        scrollTo(0, offset)
        // Safari on macOS scrolls before the page is visually changed, but
        // adding `requestAnimationFrame` doesn't fix it in this case.
      }

      clearCurrentPageTimeouts()

      $currentLocationWithoutHash = removeHash(urlToPush)

      if ($currentLocationWithoutHash in $windowEventListeners) {
        $windowEventListeners[$currentLocationWithoutHash] = []
      }

      $timers[$currentLocationWithoutHash] = {}

      applyScriptElements(function(element) {
        return !element.hasAttribute('data-instant-track')
      })

      triggerPageEvent('change', false)
    }
    else {
      // On popstate, browsers scroll by themselves, but at least Firefox
      // scrolls BEFORE popstate is fired and thus before we can replace the
      // page. If the page before popstate is too short the user won't be
      // scrolled at the right position as a result. We need to scroll again.
      scrollTo(0, scrollPosition)

      // iOS's gesture to go back by swiping from the left edge of the screen
      // will start a preloading if the user touches a link, it needs to be
      // cancelled otherwise the page behind the touched link will be
      // displayed.
      $xhr.abort()
      setPreloadingAsHalted()

      applyScriptElements(function(element) {
        return element.hasAttribute('data-instant-restore')
      })

      restoreTimers()

      triggerPageEvent('restore')
    }
  }

  function setPreloadingAsHalted() {
    $isPreloading = false
    $isWaitingForCompletion = false
  }

  function removeNoscriptTags(html) {
    // Must be done on text, not on a node's innerHTML, otherwise strange
    // things happen with implicitly closed elements (see the Noscript test).
    return html.replace(/<noscript[\s\S]+?<\/noscript>/gi, '')
  }

  function abortCurrentPageXhrs() {
    for (var i = 0; i < $currentPageXhrs.length; i++) {
      if (typeof $currentPageXhrs[i] == 'object' && 'abort' in $currentPageXhrs[i]) {
        $currentPageXhrs[i].instantclickAbort = true
        $currentPageXhrs[i].abort()
      }
    }
    $currentPageXhrs = []
  }

  function clearCurrentPageTimeouts() {
    for (var i in $timers[$currentLocationWithoutHash]) {
      var timeout = $timers[$currentLocationWithoutHash][i]
      window.clearTimeout(timeout.realId)
      timeout.delayLeft = timeout.delay - +new Date + timeout.timestamp
    }
  }

  function restoreTimers() {
    for (var i in $timers[$currentLocationWithoutHash]) {
      if (!('delayLeft' in $timers[$currentLocationWithoutHash][i])) {
        continue
      }
      var args = [
        $timers[$currentLocationWithoutHash][i].callback,
        $timers[$currentLocationWithoutHash][i].delayLeft
      ]
      for (var j = 0; j < $timers[$currentLocationWithoutHash][i].params.length; j++) {
        args.push($timers[$currentLocationWithoutHash][i].params[j])
      }
      addTimer(args, $timers[$currentLocationWithoutHash][i].isRepeating, $timers[$currentLocationWithoutHash][i].delay)
      delete $timers[$currentLocationWithoutHash][i]
    }
  }

  function handleTouchendWithoutClick() {
    $xhr.abort()
    setPreloadingAsHalted()
  }

  function addOrRemoveWindowEventListeners(addOrRemove) {
    if ($currentLocationWithoutHash in $windowEventListeners) {
      for (var i = 0; i < $windowEventListeners[$currentLocationWithoutHash].length; i++) {
        window[addOrRemove + 'EventListener'].apply(window, $windowEventListeners[$currentLocationWithoutHash][i])
      }
    }
  }

  function applyScriptElements(condition) {
    var scriptElementsInDOM = document.body.getElementsByTagName('script')
      , scriptElementsToCopy = []
      , originalElement
      , copyElement
      , parentNode
      , nextSibling
      , i

    // `scriptElementsInDOM` will change during the copy of scripts if
    // a script add or delete script elements, so we need to put script
    // elements in an array to loop through them correctly.
    for (i = 0; i < scriptElementsInDOM.length; i++) {
      scriptElementsToCopy.push(scriptElementsInDOM[i])
    }

    for (i = 0; i < scriptElementsToCopy.length; i++) {
      originalElement = scriptElementsToCopy[i]
      if (!originalElement) { // Might have disappeared, see previous comment
        continue
      }
      if (!condition(originalElement)) {
        continue
      }

      copyElement = document.createElement('script')
      for (var j = 0; j < originalElement.attributes.length; j++) {
        copyElement.setAttribute(originalElement.attributes[j].name, originalElement.attributes[j].value)
      }
      copyElement.textContent = originalElement.textContent

      parentNode = originalElement.parentNode
      nextSibling = originalElement.nextSibling
      parentNode.removeChild(originalElement)
      parentNode.insertBefore(copyElement, nextSibling)
    }
  }

  function addTrackedElements() {
    var trackedElements = document.querySelectorAll('[data-instant-track]')
      , element
      , elementData
    for (var i = 0; i < trackedElements.length; i++) {
      element = trackedElements[i]
      elementData = element.getAttribute('href') || element.getAttribute('src') || element.textContent
      // We can't use just `element.href` and `element.src` because we can't
      // retrieve `href`s and `src`s from the Ajax response.
      $trackedElementsData.push(elementData)
    }
  }

  function addTimer(args, isRepeating, realDelay) {
    var callback = args[0]
      , delay = args[1]
      , params = [].slice.call(args, 2)
      , timestamp = +new Date

    $lastUsedTimeoutId++
    var id = $lastUsedTimeoutId

    var callbackModified
    if (isRepeating) {
      callbackModified = function(args2) {
        callback(args2)
        delete $timers[$currentLocationWithoutHash][id]
        args[0] = callback
        args[1] = delay
        addTimer(args, true)
      }
    }
    else {
      callbackModified = function(args2) {
        callback(args2)
        delete $timers[$currentLocationWithoutHash][id]
      }
    }

    args[0] = callbackModified
    if (realDelay != undefined) {
      timestamp += delay - realDelay
      delay = realDelay
    }
    var realId = window.setTimeout.apply(window, args)
    $timers[$currentLocationWithoutHash][id] = {
      realId: realId,
      timestamp: timestamp,
      callback: callback,
      delay: delay,
      params: params,
      isRepeating: isRepeating
    }
    return -id
  }


  ////////// EVENT LISTENERS //////////


  function mousedownListener(event) {
    var linkElement = getParentLinkElement(event.target)

    if (!linkElement || !isPreloadable(linkElement)) {
      return
    }

    preload(linkElement.href)
  }

  function mouseoverListener(event) {
    if ($lastTouchTimestamp > (+new Date - 500)) {
      // On a touch device, if the content of the page change on mouseover
      // click is never fired and the user will need to tap a second time.
      // https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW4
      //
      // Content change could happen in the `preload` event, so we stop there.
      return
    }

    if (+new Date - $lastDisplayTimestamp < 100) {
      // After a page is displayed, if the user's cursor happens to be above
      // a link a mouseover event will be in most browsers triggered
      // automatically, and in other browsers it will be triggered when the
      // user moves his mouse by 1px.
      //
      // Here are the behaviors I noticed, all on Windows:
      // - Safari 5.1: auto-triggers after 0 ms
      // - IE 11: auto-triggers after 30-80 ms (depends on page's size?)
      // - Firefox: auto-triggers after 10 ms
      // - Opera 18: auto-triggers after 10 ms
      //
      // - Chrome: triggers when cursor moved
      // - Opera 12.16: triggers when cursor moved
      //
      // To remedy to this, we do nothing if the last display occurred less
      // than 100 ms ago.

      return
    }

    var linkElement = getParentLinkElement(event.target)

    if (!linkElement) {
      return
    }

    if (linkElement == getParentLinkElement(event.relatedTarget)) {
      // Happens when mouseout-ing and mouseover-ing child elements of the same link element
      return
    }

    if (!isPreloadable(linkElement)) {
      return
    }

    linkElement.addEventListener('mouseout', mouseoutListener)

    if (!$isWaitingForCompletion) {
      $urlToPreload = linkElement.href
      $preloadTimer = setTimeout(preload, $delayBeforePreload)
    }
  }

  function touchstartListener(event) {
    $lastTouchTimestamp = +new Date

    var linkElement = getParentLinkElement(event.target)

    if (!linkElement || !isPreloadable(linkElement)) {
      return
    }

    if ($touchEndedWithoutClickTimer) {
      clearTimeout($touchEndedWithoutClickTimer)
      $touchEndedWithoutClickTimer = false
    }

    linkElement.addEventListener('touchend', touchendAndTouchcancelListener)
    linkElement.addEventListener('touchcancel', touchendAndTouchcancelListener)

    preload(linkElement.href)
  }

  function clickListenerPrelude() {
    // Makes clickListener be fired after everyone else, so that we can respect
    // event.preventDefault.
    document.addEventListener('click', clickListener)
  }

  function clickListener(event) {
    document.removeEventListener('click', clickListener)

    if ($touchEndedWithoutClickTimer) {
      clearTimeout($touchEndedWithoutClickTimer)
      $touchEndedWithoutClickTimer = false
    }

    if (event.defaultPrevented) {
      return
    }

    var linkElement = getParentLinkElement(event.target)

    if (!linkElement || !isPreloadable(linkElement)) {
      return
    }

    // Check if it's opening in a new tab
    if (event.button != 0 // Chrome < 55 fires a click event when the middle mouse button is pressed
      || event.metaKey
      || event.ctrlKey) {
      return
    }
    event.preventDefault()
    display(linkElement.href)
  }

  function mouseoutListener(event) {
    if (getParentLinkElement(event.target) == getParentLinkElement(event.relatedTarget)) {
      // Happens when mouseout-ing and mouseover-ing child elements of the same link element,
      // we don't want to stop preloading then.
      return
    }

    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
      return
    }

    if (!$isPreloading || $isWaitingForCompletion) {
      return
    }

    $xhr.abort()
    setPreloadingAsHalted()
  }

  function touchendAndTouchcancelListener(event) {
    if (!$isPreloading || $isWaitingForCompletion) {
      return
    }

    $touchEndedWithoutClickTimer = setTimeout(handleTouchendWithoutClick, 500)
  }

  function readystatechangeListener() {
    if ($xhr.readyState == 2) { // headers received
      var contentType = $xhr.getResponseHeader('Content-Type')
      if (!contentType || !/^text\/html/i.test(contentType)) {
        $isContentTypeNotHTML = true
      }
    }

    if ($xhr.readyState < 4) {
      return
    }

    if ($xhr.status == 0) {
      // Request error/timeout/abort
      $gotANetworkError = true
      if ($isWaitingForCompletion) {
        triggerPageEvent('exit', $url, 'network error')
        location.href = $url
      }
      return
    }

    if ($isContentTypeNotHTML) {
      if ($isWaitingForCompletion) {
        triggerPageEvent('exit', $url, 'non-html content-type')
        location.href = $url
      }
      return
    }

    var doc = document.implementation.createHTMLDocument('')
    doc.documentElement.innerHTML = removeNoscriptTags($xhr.responseText)
    $title = doc.title
    $body = doc.body

    var alteredOnReceive = triggerPageEvent('receive', $url, $body, $title)
    if (alteredOnReceive) {
      if ('body' in alteredOnReceive) {
        $body = alteredOnReceive.body
      }
      if ('title' in alteredOnReceive) {
        $title = alteredOnReceive.title
      }
    }

    var urlWithoutHash = removeHash($url)
    $history[urlWithoutHash] = {
      body: $body,
      title: $title,
      scrollPosition: urlWithoutHash in $history ? $history[urlWithoutHash].scrollPosition : 0
    }

    var trackedElements = doc.querySelectorAll('[data-instant-track]')
      , element
      , elementData

    if (trackedElements.length != $trackedElementsData.length) {
      $areTrackedElementsDifferent = true
    }
    else {
      for (var i = 0; i < trackedElements.length; i++) {
        element = trackedElements[i]
        elementData = element.getAttribute('href') || element.getAttribute('src') || element.textContent
        if ($trackedElementsData.indexOf(elementData) == -1) {
          $areTrackedElementsDifferent = true
        }
      }
    }

    if ($isWaitingForCompletion) {
      $isWaitingForCompletion = false
      display($url)
    }
  }

  function popstateListener() {
    var loc = removeHash(location.href)
    if (loc == $currentLocationWithoutHash) {
      return
    }

    if ($isWaitingForCompletion) {
      setPreloadingAsHalted()
      $xhr.abort()
    }

    if (!(loc in $history)) {
      triggerPageEvent('exit', location.href, 'not in history')
      if (loc == location.href) { // no location.hash
        location.href = location.href
        // Reloads the page while using cache for scripts, styles and images,
        // unlike `location.reload()`
      }
      else {
        // When there's a hash, `location.href = location.href` won't reload
        // the page (but will trigger a popstate event, thus causing an infinite
        // loop), so we need to call `location.reload()`
        location.reload()
      }
      return
    }

    $history[$currentLocationWithoutHash].scrollPosition = pageYOffset
    clearCurrentPageTimeouts()
    addOrRemoveWindowEventListeners('remove')
    $currentLocationWithoutHash = loc
    changePage($history[loc].title, $history[loc].body, false, $history[loc].scrollPosition)
    addOrRemoveWindowEventListeners('add')
  }


  ////////// MAIN FUNCTIONS //////////


  function preload(url) {
    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
    }

    if (!url) {
      url = $urlToPreload
    }

    if ($isPreloading && (url == $url || $isWaitingForCompletion)) {
      return
    }
    $isPreloading = true
    $isWaitingForCompletion = false

    $url = url
    $body = false
    $isContentTypeNotHTML = false
    $gotANetworkError = false
    $areTrackedElementsDifferent = false
    triggerPageEvent('preload')
    $xhr.open('GET', url)
    $xhr.timeout = 90000 // Must be set after `open()` with IE
    $xhr.send()
  }

  function display(url) {
    $lastDisplayTimestamp = +new Date
    if ($preloadTimer || !$isPreloading) {
      // $preloadTimer:
      // Happens when there's a delay before preloading and that delay
      // hasn't expired (preloading didn't kick in).
      //
      // !$isPreloading:
      // A link has been clicked, and preloading hasn't been initiated.
      // It happens with touch devices when a user taps *near* the link,
      // causing `touchstart` not to be fired. Safari/Chrome will trigger
      // `mouseover`, `mousedown`, `click` (and others), but when that happens
      // we do nothing in `mouseover` as it may cause `click` not to fire (see
      // comment in `mouseoverListener`).
      //
      // It also happens when a user uses his keyboard to navigate (with Tab
      // and Return), and possibly in other non-mainstream ways to navigate
      // a website.

      if ($preloadTimer && $url && $url != url) {
        // Happens when the user clicks on a link before preloading
        // kicks in while another link is already preloading.

        triggerPageEvent('exit', url, 'click occured while preloading planned')
        location.href = url
        return
      }

      preload(url)
      triggerPageEvent('wait')
      $isWaitingForCompletion = true // Must be set *after* calling `preload`
      return
    }
    if ($isWaitingForCompletion) {
      // The user clicked on a link while a page to display was preloading.
      // Either on the same link or on another link. If it's the same link
      // something might have gone wrong (or he could have double clicked, we
      // don't handle that case), so we send him to the page without pjax.
      // If it's another link, it hasn't been preloaded, so we redirect the
      // user to it.
      triggerPageEvent('exit', url, 'clicked on a link while waiting for another page to display')
      location.href = url
      return
    }
    if ($isContentTypeNotHTML) {
      triggerPageEvent('exit', $url, 'non-html content-type')
      location.href = $url
      return
    }
    if ($gotANetworkError) {
      triggerPageEvent('exit', $url, 'network error')
      location.href = $url
      return
    }
    if ($areTrackedElementsDifferent) {
      triggerPageEvent('exit', $url, 'different assets')
      location.href = $url
      return
    }
    if (!$body) {
      triggerPageEvent('wait')
      $isWaitingForCompletion = true
      return
    }
    $history[$currentLocationWithoutHash].scrollPosition = pageYOffset
    setPreloadingAsHalted()
    changePage($title, $body, $url)
  }


  ////////// PUBLIC VARIABLE AND FUNCTIONS //////////


  var supported = false
  if ('pushState' in history
      && location.protocol != "file:") {
    supported = true

    var indexOfAndroid = $userAgent.indexOf('Android ')
    if (indexOfAndroid > -1) {
      // The stock browser in Android 4.0.3 through 4.3.1 supports pushState,
      // though it doesn't update the address bar.
      //
      // More problematic is that it has a bug on `popstate` when coming back
      // from a page not displayed through InstantClick: `location.href` is
      // undefined and `location.reload()` doesn't work.
      //
      // Android < 4.4 is therefore blacklisted, unless it's a browser known
      // not to have that latter bug.

      var androidVersion = parseFloat($userAgent.substr(indexOfAndroid + 'Android '.length))
      if (androidVersion < 4.4) {
        supported = false
        if (androidVersion >= 4) {
          var whitelistedBrowsersUserAgentsOnAndroid4 = [
            / Chrome\//, // Chrome, Opera, Puffin, QQ, Yandex
            / UCBrowser\//,
            / Firefox\//,
            / Windows Phone /, // WP 8.1+ pretends to be Android
          ]
          for (var i = 0; i < whitelistedBrowsersUserAgentsOnAndroid4.length; i++) {
            if (whitelistedBrowsersUserAgentsOnAndroid4[i].test($userAgent)) {
              supported = true
              break
            }
          }
        }
      }
    }
  }

  function init(preloadingMode) {
    if (!supported) {
      triggerPageEvent('change', true)
      return
    }

    if ($hasBeenInitialized) {
      return
    }
    $hasBeenInitialized = true

    if (preloadingMode == 'mousedown') {
      $preloadOnMousedown = true
    }
    else if (typeof preloadingMode == 'number') {
      $delayBeforePreload = preloadingMode
    }

    $currentLocationWithoutHash = removeHash(location.href)
    $timers[$currentLocationWithoutHash] = {}
    $history[$currentLocationWithoutHash] = {
      body: document.body,
      title: document.title,
      scrollPosition: pageYOffset
    }

    if (document.readyState == 'loading') {
      document.addEventListener('DOMContentLoaded', addTrackedElements)
    }
    else {
      addTrackedElements()
    }

    $xhr = new XMLHttpRequest()
    $xhr.addEventListener('readystatechange', readystatechangeListener)

    document.addEventListener('touchstart', touchstartListener, true)
    if ($preloadOnMousedown) {
      document.addEventListener('mousedown', mousedownListener, true)
    }
    else {
      document.addEventListener('mouseover', mouseoverListener, true)
    }
    document.addEventListener('click', clickListenerPrelude, true)

    addEventListener('popstate', popstateListener)
  }

  function on(eventType, callback) {
    $eventsCallbacks[eventType].push(callback)

    if (eventType == 'change') {
      callback(!$lastDisplayTimestamp)
    }
  }

  function setTimeout() {
    return addTimer(arguments, false)
  }

  function setInterval() {
    return addTimer(arguments, true)
  }

  function clearTimeout(id) {
    id = -id
    for (var loc in $timers) {
      if (id in $timers[loc]) {
        window.clearTimeout($timers[loc][id].realId)
        delete $timers[loc][id]
      }
    }
  }

  function xhr(xhr) {
    $currentPageXhrs.push(xhr)
  }

  function addPageEvent() {
    if (!($currentLocationWithoutHash in $windowEventListeners)) {
      $windowEventListeners[$currentLocationWithoutHash] = []
    }
    $windowEventListeners[$currentLocationWithoutHash].push(arguments)
    addEventListener.apply(window, arguments)
  }

  function removePageEvent() {
    if (!($currentLocationWithoutHash in $windowEventListeners)) {
      return
    }
    firstLoop:
    for (var i = 0; i < $windowEventListeners[$currentLocationWithoutHash].length; i++) {
      if (arguments.length != $windowEventListeners[$currentLocationWithoutHash][i].length) {
        continue
      }
      for (var j = 0; j < $windowEventListeners[$currentLocationWithoutHash][i].length; j++) {
        if (arguments[j] != $windowEventListeners[$currentLocationWithoutHash][i][j]) {
          continue firstLoop
        }
      }
      $windowEventListeners[$currentLocationWithoutHash].splice(i, 1)
    }
  }

  function addEvent(selector, type, listener) {
    if (!(type in $delegatedEvents)) {
      $delegatedEvents[type] = {}

      document.addEventListener(type, function(event) {
        var element = event.target
        event.originalStopPropagation = event.stopPropagation
        event.stopPropagation = function() {
          this.isPropagationStopped = true
          this.originalStopPropagation()
        }
        while (element && element.nodeType == 1) {
          for (var selector in $delegatedEvents[type]) {
            if (element.matches(selector)) {
              for (var i = 0; i < $delegatedEvents[type][selector].length; i++) {
                $delegatedEvents[type][selector][i].call(element, event)
              }
              if (event.isPropagationStopped) {
                return
              }
              break
            }
          }
          element = element.parentNode
        }
      }, false) // Third parameter isn't optional in Firefox < 6

      if (type == 'click' && /iP(?:hone|ad|od)/.test($userAgent)) {
        // Force Mobile Safari to trigger the click event on document by adding a pointer cursor to body

        var styleElement = document.createElement('style')
        styleElement.setAttribute('instantclick-mobile-safari-cursor', '') // So that this style element doesn't surprise developers in the browser DOM inspector.
        styleElement.textContent = 'body { cursor: pointer !important; }'
        document.head.appendChild(styleElement)
      }
    }

    if (!(selector in $delegatedEvents[type])) {
      $delegatedEvents[type][selector] = []
    }

    // Run removeEvent beforehand so that it can't be added twice
    removeEvent(selector, type, listener)

    $delegatedEvents[type][selector].push(listener)
  }

  function removeEvent(selector, type, listener) {
    var index = $delegatedEvents[type][selector].indexOf(listener)
    if (index > -1) {
      $delegatedEvents[type][selector].splice(index, 1)
    }
  }


  ////////////////////


  return {
    supported: supported,
    init: init,
    on: on,
    setTimeout: setTimeout,
    setInterval: setInterval,
    clearTimeout: clearTimeout,
    xhr: xhr,
    addPageEvent: addPageEvent,
    removePageEvent: removePageEvent,
    addEvent: addEvent,
    removeEvent: removeEvent
  }

}(document, location, navigator.userAgent);

/* InstantClick's loading indicator | (C) 2014-2017 Alexandre Dieulot | http://instantclick.io/license */

;(function() {
  var $element
    , $timer

  function init() {
    $element = document.createElement('div')
    $element.id = 'instantclick'

    var vendors = {
          Webkit: true,
          Moz: true
        }
      , vendorPrefix = ''

    if (!('transform' in $element.style)) {
      for (var vendor in vendors) {
        if (vendor + 'Transform' in $element.style) {
          vendorPrefix = '-' + vendor.toLowerCase() + '-'
        }
      }
    }

    var styleElement = document.createElement('style')
    styleElement.setAttribute('instantclick-loading-indicator', '') // So that this style element doesn't surprise developers in the browser DOM inspector.
    styleElement.textContent = '#instantclick {pointer-events:none; z-index:2147483647; position:fixed; top:0; left:0; width:100%; height:3px; border-radius:2px; color:hsl(192,100%,50%); background:currentColor; box-shadow: 0 -1px 8px; opacity: 0;}' +
                               '#instantclick.visible {opacity:1; ' + vendorPrefix + 'animation:instantclick .6s linear infinite;}' +
                               '@' + vendorPrefix + 'keyframes instantclick {0%,5% {' + vendorPrefix + 'transform:translateX(-100%);} 45%,55% {' + vendorPrefix + 'transform:translateX(0%);} 95%,100% {' + vendorPrefix + 'transform:translateX(100%);}}'
    document.head.appendChild(styleElement)
  }

  function changeListener(isInitialPage) {
    if (!instantclick.supported) {
      return
    }

    if (isInitialPage) {
      init()
    }

    document.body.appendChild($element)

    if (!isInitialPage) {
      hide()
    }
  }

  function restoreListener() {
    document.body.appendChild($element)

    hide()
  }

  function waitListener() {
    $timer = instantclick.setTimeout(show, 800)
  }

  function show() {
    $element.className = 'visible'
  }

  function hide() {
    instantclick.clearTimeout($timer)

    $element.className = ''
    // Doesn't work (has no visible effect) in Safari on `exit`.
    //
    // My guess is that Safari queues styling change for the next frame and
    // drops that queue on location change.
  }


  ////////////////////


  instantclick.on('change', changeListener)
  instantclick.on('restore', restoreListener)
  instantclick.on('wait', waitListener)
  instantclick.on('exit', hide)


  ////////////////////


  instantclick.loadingIndicator = {
    show: show,
    hide: hide
  }
})();

instantclick.init();