diff --git a/assets/js/instantclick.js b/assets/js/instantclick.js index a2539d8..0787beb 100644 --- a/assets/js/instantclick.js +++ b/assets/js/instantclick.js @@ -1,13 +1,757 @@ /* InstantClick 3.1.0 | (C) 2014 Alexandre Dieulot | http://instantclick.io/license */ -var InstantClick=function(d,e){function w(a){var b=a.indexOf("#");return 0>b?a:a.substr(0,b)}function z(a){for(;a&&"A"!=a.nodeName;)a=a.parentNode;return a}function A(a){var b=e.protocol+"//"+e.host;if(!(b=a.target||a.hasAttribute("download")||0!=a.href.indexOf(b+"/")||-1+new Date-500||(a=z(a.target))&&A(a)&&x(a.href)}function N(a){G>+new Date-500||(a=z(a.target))&&A(a)&&(a.addEventListener("mouseout",T),H?(O=a.href,l=setTimeout(x,H)):x(a.href))}function U(a){G=+new Date;(a=z(a.target))&&A(a)&&(D?a.removeEventListener("mousedown", -M):a.removeEventListener("mouseover",N),x(a.href))}function V(a){var b=z(a.target);!b||!A(b)||1p.readyState)&&0!=p.status){q.ready=+new Date-q.start;if(p.getResponseHeader("Content-Type").match(/\/(x|ht|xht)ml/)){var a=d.implementation.createHTMLDocument("");a.documentElement.innerHTML=p.responseText.replace(//gi,"");y=a.title; -u=a.body;var b=t("receive",r,u,y);b&&("body"in b&&(u=b.body),"title"in b&&(y=b.title));b=w(r);h[b]={body:u,title:y,scrollY:b in h?h[b].scrollY:0};for(var a=a.head.children,b=0,c,g=a.length-1;0<=g;g--)if(c=a[g],c.hasAttribute("data-instant-track")){c=c.getAttribute("href")||c.getAttribute("src")||c.innerHTML;for(var e=E.length-1;0<=e;e--)E[e]==c&&b++}b!=E.length&&(F=!0)}else F=!0;m&&(m=!1,P(r))}}function L(a){d.body.addEventListener("touchstart",U,!0);D?d.body.addEventListener("mousedown",M,!0):d.body.addEventListener("mouseover", -N,!0);d.body.addEventListener("click",V,!0);if(!a){a=d.body.getElementsByTagName("script");var b,c,g,e;i=0;for(j=a.length;i+new Date-(q.start+q.display)||(l&&(clearTimeout(l),l=!1),a||(a=O),v&&(a==r||m))||(v=!0,m=!1,r=a,F=u=!1,q={start:+new Date},t("fetch"), -p.open("GET",a),p.send())}function P(a){"display"in q||(q.display=+new Date-q.start);l||!v?l&&r&&r!=a?e.href=a:(x(a),C.start(0,!0),t("wait"),m=!0):m?e.href=a:F?e.href=r:u?(h[k].scrollY=pageYOffset,m=v=!1,K(y,u,r)):(C.start(0,!0),t("wait"),m=!0)}var I=navigator.userAgent,S=-1b;b++)a[b]+"Transform"in h.style&&(k=a[b]+"Transform");var c="transition";if(!(c in h.style))for(b=0;3>b;b++)a[b]+"Transition"in -h.style&&(c="-"+a[b].toLowerCase()+"-"+c);a=d.createElement("style");a.innerHTML="#instantclick{position:"+(Q?"absolute":"fixed")+";top:0;left:0;width:100%;pointer-events:none;z-index:2147483647;"+c+":opacity .25s .1s}.instantclick-bar{background:#29d;width:100%;margin-left:-100%;height:2px;"+c+":all .25s}";d.head.appendChild(a);Q&&(m(),addEventListener("resize",m),addEventListener("scroll",m))},start:a,done:e}}(),R="pushState"in history&&(!I.match("Android")||I.match("Chrome/"))&&"file:"!=e.protocol; -return{supported:R,init:function(){if(!k)if(R){for(var a=arguments.length-1;0<=a;a--){var b=arguments[a];!0===b?J=!0:"mousedown"==b?D=!0:"number"==typeof b&&(H=b)}k=w(e.href);h[k]={body:d.body,title:d.title,scrollY:pageYOffset};for(var b=d.head.children,c,a=b.length-1;0<=a;a--)c=b[a],c.hasAttribute("data-instant-track")&&(c=c.getAttribute("href")||c.getAttribute("src")||c.innerHTML,E.push(c));p=new XMLHttpRequest;p.addEventListener("readystatechange",W);L(!0);C.init();t("change",!0);addEventListener("popstate", -function(){var a=w(e.href);a!=k&&(a in h?(h[k].scrollY=pageYOffset,k=a,K(h[a].title,h[a].body,!1,h[a].scrollY)):e.href=e.href)})}else t("change",!0)},on:function(a,b){B[a].push(b)}}}(document,location); + +var InstantClick = function(document, location) { + // Internal variables + var $ua = navigator.userAgent, + $isChromeForIOS = $ua.indexOf(' CriOS/') > -1, + $hasTouch = 'createTouch' in document, + $currentLocationWithoutHash, + $urlToPreload, + $preloadTimer, + $lastTouchTimestamp, + + // Preloading-related variables + $history = {}, + $xhr, + $url = false, + $title = false, + $mustRedirect = false, + $body = false, + $timing = {}, + $isPreloading = false, + $isWaitingForCompletion = false, + $trackedAssets = [], + + // Variables defined by public functions + $useWhitelist, + $preloadOnMousedown, + $delayBeforePreload, + $eventsCallbacks = { + fetch: [], + receive: [], + wait: [], + change: [] + } + + + ////////// HELPERS ////////// + + + function removeHash(url) { + var index = url.indexOf('#') + if (index < 0) { + return url + } + return url.substr(0, index) + } + + function getLinkTarget(target) { + while (target && target.nodeName != 'A') { + target = target.parentNode + } + return target + } + + function isBlacklisted(elem) { + do { + if (!elem.hasAttribute) { // Parent of + break + } + if (elem.hasAttribute('data-instant')) { + return false + } + if (elem.hasAttribute('data-no-instant')) { + return true + } + } + while (elem = elem.parentNode); + return false + } + + function isWhitelisted(elem) { + do { + if (!elem.hasAttribute) { // Parent of + break + } + if (elem.hasAttribute('data-no-instant')) { + return false + } + if (elem.hasAttribute('data-instant')) { + return true + } + } + while (elem = elem.parentNode); + return false + } + + function isPreloadable(a) { + var domain = location.protocol + '//' + location.host + + if (a.target // target="_blank" etc. + || a.hasAttribute('download') + || a.href.indexOf(domain + '/') != 0 // Another domain, or no href attribute + || (a.href.indexOf('#') > -1 + && removeHash(a.href) == $currentLocationWithoutHash) // Anchor + || ($useWhitelist + ? !isWhitelisted(a) + : isBlacklisted(a)) + ) { + return false + } + return true + } + + function triggerPageEvent(eventType, arg1, arg2, arg3) { + var returnValue = false + for (var i = 0; i < $eventsCallbacks[eventType].length; i++) { + if (eventType == 'receive') { + var altered = $eventsCallbacks[eventType][i](arg1, arg2, arg3) + if (altered) { + /* Update args for the next iteration of the loop. */ + if ('body' in altered) { + arg2 = altered.body + } + if ('title' in altered) { + arg3 = altered.title + } + + returnValue = altered + } + } + else { + $eventsCallbacks[eventType][i](arg1, arg2, arg3) + } + } + return returnValue + } + + function changePage(title, body, newUrl, scrollY) { + 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. + */ + + if (newUrl) { + history.pushState(null, null, newUrl) + + var hashIndex = newUrl.indexOf('#'), + hashElem = hashIndex > -1 + && document.getElementById(newUrl.substr(hashIndex + 1)), + offset = 0 + + if (hashElem) { + while (hashElem.offsetParent) { + offset += hashElem.offsetTop + + hashElem = hashElem.offsetParent + } + } + scrollTo(0, offset) + + $currentLocationWithoutHash = removeHash(newUrl) + } + else { + scrollTo(0, scrollY) + } + + if ($isChromeForIOS && document.title == title) { + /* Chrome for iOS: + * + * 1. Removes title on pushState, so the title needs to be set after. + * + * 2. Will not set the title if it’s identical when trimmed, so + * appending a space won't do, but a non-breaking space works. + */ + document.title = title + String.fromCharCode(160) + } + else { + document.title = title + } + + instantanize() + bar.done() + triggerPageEvent('change', false) + + // Real event, useful for combining userscripts, but only for that so it’s undocumented. + var userscriptEvent = document.createEvent('HTMLEvents') + userscriptEvent.initEvent('instantclick:newpage', true, true) + dispatchEvent(userscriptEvent) + } + + 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(//gi, '') + } + + + ////////// EVENT HANDLERS ////////// + + + function mousedown(e) { + if ($lastTouchTimestamp > (+new Date - 500)) { + return // Otherwise, click doesn’t fire + } + + var a = getLinkTarget(e.target) + + if (!a || !isPreloadable(a)) { + return + } + + preload(a.href) + } + + function mouseover(e) { + if ($lastTouchTimestamp > (+new Date - 500)) { + return // Otherwise, click doesn’t fire + } + + var a = getLinkTarget(e.target) + + if (!a || !isPreloadable(a)) { + return + } + + a.addEventListener('mouseout', mouseout) + + if (!$delayBeforePreload) { + preload(a.href) + } + else { + $urlToPreload = a.href + $preloadTimer = setTimeout(preload, $delayBeforePreload) + } + } + + function touchstart(e) { + $lastTouchTimestamp = +new Date + + var a = getLinkTarget(e.target) + + if (!a || !isPreloadable(a)) { + return + } + + if ($preloadOnMousedown) { + a.removeEventListener('mousedown', mousedown) + } + else { + a.removeEventListener('mouseover', mouseover) + } + preload(a.href) + } + + function click(e) { + var a = getLinkTarget(e.target) + + if (!a || !isPreloadable(a)) { + return + } + + if (e.which > 1 || e.metaKey || e.ctrlKey) { // Opening in new tab + return + } + e.preventDefault() + display(a.href) + } + + function mouseout() { + if ($preloadTimer) { + clearTimeout($preloadTimer) + $preloadTimer = false + return + } + + if (!$isPreloading || $isWaitingForCompletion) { + return + } + $xhr.abort() + setPreloadingAsHalted() + } + + function readystatechange() { + if ($xhr.readyState < 4) { + return + } + if ($xhr.status == 0) { + /* Request aborted */ + return + } + + $timing.ready = +new Date - $timing.start + + if ($xhr.getResponseHeader('Content-Type').match(/\/(x|ht|xht)ml/)) { + 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, + scrollY: urlWithoutHash in $history ? $history[urlWithoutHash].scrollY : 0 + } + + var elems = doc.head.children, + found = 0, + elem, + data + + for (var i = elems.length - 1; i >= 0; i--) { + elem = elems[i] + if (elem.hasAttribute('data-instant-track')) { + data = elem.getAttribute('href') || elem.getAttribute('src') || elem.innerHTML + for (var j = $trackedAssets.length - 1; j >= 0; j--) { + if ($trackedAssets[j] == data) { + found++ + } + } + } + } + if (found != $trackedAssets.length) { + $mustRedirect = true // Assets have changed + } + } + else { + $mustRedirect = true // Not an HTML document + } + + if ($isWaitingForCompletion) { + $isWaitingForCompletion = false + display($url) + } + } + + + ////////// MAIN FUNCTIONS ////////// + + + function instantanize(isInitializing) { + document.body.addEventListener('touchstart', touchstart, true) + if ($preloadOnMousedown) { + document.body.addEventListener('mousedown', mousedown, true) + } + else { + document.body.addEventListener('mouseover', mouseover, true) + } + document.body.addEventListener('click', click, true) + + if (!isInitializing) { + var scripts = document.body.getElementsByTagName('script'), + script, + copy, + parentNode, + nextSibling + + for (i = 0, j = scripts.length; i < j; i++) { + script = scripts[i] + if (script.hasAttribute('data-no-instant')) { + continue + } + copy = document.createElement('script') + if (script.src) { + copy.src = script.src + } + if (script.innerHTML) { + copy.innerHTML = script.innerHTML + } + parentNode = script.parentNode + nextSibling = script.nextSibling + parentNode.removeChild(script) + parentNode.insertBefore(copy, nextSibling) + } + } + } + + function preload(url) { + if (!$preloadOnMousedown + && 'display' in $timing + && +new Date - ($timing.start + $timing.display) < 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 behavior 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 not start preloading if last display + occurred less than 100 ms ago. If they happen to click on the link, + they will be redirected. + */ + + return + } + if ($preloadTimer) { + clearTimeout($preloadTimer) + $preloadTimer = false + } + + if (!url) { + url = $urlToPreload + } + + if ($isPreloading && (url == $url || $isWaitingForCompletion)) { + return + } + $isPreloading = true + $isWaitingForCompletion = false + + $url = url + $body = false + $mustRedirect = false + $timing = { + start: +new Date + } + triggerPageEvent('fetch') + $xhr.open('GET', url) + $xhr.send() + } + + function display(url) { + if (!('display' in $timing)) { + $timing.display = +new Date - $timing.start + } + 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, + Safari/Chrome will trigger mousedown, mouseover, click (and others), + but when that happens we ignore mousedown/mouseover (otherwise click + doesn’t fire). Maybe there’s a way to make the click event fire, but + that’s not worth it as mousedown/over happen just 1ms before click + in this situation. + + 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. + */ + + location.href = url + return + } + + preload(url) + bar.start(0, true) + triggerPageEvent('wait') + $isWaitingForCompletion = true // Must be set *after* calling `preload` + return + } + if ($isWaitingForCompletion) { + /* The user clicked on a link while a page 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. + */ + location.href = url + return + } + if ($mustRedirect) { + location.href = $url + return + } + if (!$body) { + bar.start(0, true) + triggerPageEvent('wait') + $isWaitingForCompletion = true + return + } + $history[$currentLocationWithoutHash].scrollY = pageYOffset + setPreloadingAsHalted() + changePage($title, $body, $url) + } + + + ////////// PROGRESS BAR FUNCTIONS ////////// + + + var bar = function() { + var $barContainer, + $barElement, + $barTransformProperty, + $barProgress, + $barTimer + + function init() { + $barContainer = document.createElement('div') + $barContainer.id = 'instantclick' + $barElement = document.createElement('div') + $barElement.id = 'instantclick-bar' + $barElement.className = 'instantclick-bar' + $barContainer.appendChild($barElement) + + var vendors = ['Webkit', 'Moz', 'O'] + + $barTransformProperty = 'transform' + if (!($barTransformProperty in $barElement.style)) { + for (var i = 0; i < 3; i++) { + if (vendors[i] + 'Transform' in $barElement.style) { + $barTransformProperty = vendors[i] + 'Transform' + } + } + } + + var transitionProperty = 'transition' + if (!(transitionProperty in $barElement.style)) { + for (var i = 0; i < 3; i++) { + if (vendors[i] + 'Transition' in $barElement.style) { + transitionProperty = '-' + vendors[i].toLowerCase() + '-' + transitionProperty + } + } + } + + var style = document.createElement('style') + style.innerHTML = '#instantclick{position:' + ($hasTouch ? 'absolute' : 'fixed') + ';top:0;left:0;width:100%;pointer-events:none;z-index:2147483647;' + transitionProperty + ':opacity .25s .1s}' + + '.instantclick-bar{background:#29d;width:100%;margin-left:-100%;height:2px;' + transitionProperty + ':all .25s}' + /* We set the bar's background in `.instantclick-bar` so that it can be + overriden in CSS with `#instantclick-bar`, as IDs have higher priority. + */ + document.head.appendChild(style) + + if ($hasTouch) { + updatePositionAndScale() + addEventListener('resize', updatePositionAndScale) + addEventListener('scroll', updatePositionAndScale) + } + + } + + function start(at, jump) { + $barProgress = at + if (document.getElementById($barContainer.id)) { + document.body.removeChild($barContainer) + } + $barContainer.style.opacity = '1' + if (document.getElementById($barContainer.id)) { + document.body.removeChild($barContainer) + /* So there's no CSS animation if already done once and it goes from 1 to 0 */ + } + update() + if (jump) { + setTimeout(jumpStart, 0) + /* Must be done in a timer, otherwise the CSS animation doesn't happen. */ + } + clearTimeout($barTimer) + $barTimer = setTimeout(inc, 500) + } + + function jumpStart() { + $barProgress = 10 + update() + } + + function inc() { + $barProgress += 1 + (Math.random() * 2) + if ($barProgress >= 98) { + $barProgress = 98 + } + else { + $barTimer = setTimeout(inc, 500) + } + update() + } + + function update() { + $barElement.style[$barTransformProperty] = 'translate(' + $barProgress + '%)' + if (!document.getElementById($barContainer.id)) { + document.body.appendChild($barContainer) + } + } + + function done() { + if (document.getElementById($barContainer.id)) { + clearTimeout($barTimer) + $barProgress = 100 + update() + $barContainer.style.opacity = '0' + /* If you're debugging, setting this to 0.5 is handy. */ + return + } + + /* The bar container hasn't been appended: It's a new page. */ + start($barProgress == 100 ? 0 : $barProgress) + /* $barProgress is 100 on popstate, usually. */ + setTimeout(done, 0) + /* Must be done in a timer, otherwise the CSS animation doesn't happen. */ + } + + function updatePositionAndScale() { + /* Adapted from code by Sam Stephenson and Mislav Marohnić + http://signalvnoise.com/posts/2407 + */ + + $barContainer.style.left = pageXOffset + 'px' + $barContainer.style.width = innerWidth + 'px' + $barContainer.style.top = pageYOffset + 'px' + + var landscape = 'orientation' in window && Math.abs(orientation) == 90, + scaleY = innerWidth / screen[landscape ? 'height' : 'width'] * 2 + /* We multiply the size by 2 because the progress bar is harder + to notice on a mobile device. + */ + $barContainer.style[$barTransformProperty] = 'scaleY(' + scaleY + ')' + } + + return { + init: init, + start: start, + done: done + } + }() + + + ////////// PUBLIC VARIABLE AND FUNCTIONS ////////// + + var supported = 'pushState' in history + && (!$ua.match('Android') || $ua.match('Chrome/')) + && location.protocol != "file:" + + /* The state of Android's AOSP browsers: + + 2.3.7: pushState appears to work correctly, but + `doc.documentElement.innerHTML = body` is buggy. + See details here: http://stackoverflow.com/q/21918564 + Not an issue anymore, but it may fail where 3.0 do, this needs + testing again. + + 3.0: pushState appears to work correctly (though the URL bar is only + updated on focus), but + `document.documentElement.replaceChild(doc.body, document.body)` + throws DOMException: WRONG_DOCUMENT_ERR. + + 4.0.2: Doesn't support pushState. + + 4.0.4, + 4.1.1, + 4.2, + 4.3: pushState is here, but it doesn't update the URL bar. + (Great logic there.) + + 4.4: Works correctly. Claims to be 'Chrome/30.0.0.0'. + + All androids tested with Android SDK's Emulator. + Version numbers are from the browser's user agent. + + Because of this mess, the only whitelisted browser on Android is Chrome. + */ + + function init() { + if ($currentLocationWithoutHash) { + /* Already initialized */ + return + } + if (!supported) { + triggerPageEvent('change', true) + return + } + for (var i = arguments.length - 1; i >= 0; i--) { + var arg = arguments[i] + if (arg === true) { + $useWhitelist = true + } + else if (arg == 'mousedown') { + $preloadOnMousedown = true + } + else if (typeof arg == 'number') { + $delayBeforePreload = arg + } + } + $currentLocationWithoutHash = removeHash(location.href) + $history[$currentLocationWithoutHash] = { + body: document.body, + title: document.title, + scrollY: pageYOffset + } + + var elems = document.head.children, + elem, + data + for (var i = elems.length - 1; i >= 0; i--) { + elem = elems[i] + if (elem.hasAttribute('data-instant-track')) { + data = elem.getAttribute('href') || elem.getAttribute('src') || elem.innerHTML + /* We can't use just `elem.href` and `elem.src` because we can't + retrieve `href`s and `src`s from the Ajax response. + */ + $trackedAssets.push(data) + } + } + + $xhr = new XMLHttpRequest() + $xhr.addEventListener('readystatechange', readystatechange) + + instantanize(true) + + bar.init() + + triggerPageEvent('change', true) + + addEventListener('popstate', function() { + var loc = removeHash(location.href) + if (loc == $currentLocationWithoutHash) { + return + } + + if (!(loc in $history)) { + location.href = location.href + /* Reloads the page while using cache for scripts, styles and images, + unlike `location.reload()` */ + return + } + + $history[$currentLocationWithoutHash].scrollY = pageYOffset + $currentLocationWithoutHash = loc + changePage($history[loc].title, $history[loc].body, false, $history[loc].scrollY) + }) + } + + function on(eventType, callback) { + $eventsCallbacks[eventType].push(callback) + } + + + //////////////////// + + + return { + supported: supported, + init: init, + on: on + } + +}(document, location); diff --git a/assets/js/instantclick.min.js b/assets/js/instantclick.min.js new file mode 100644 index 0000000..a2539d8 --- /dev/null +++ b/assets/js/instantclick.min.js @@ -0,0 +1,13 @@ +/* InstantClick 3.1.0 | (C) 2014 Alexandre Dieulot | http://instantclick.io/license */ +var InstantClick=function(d,e){function w(a){var b=a.indexOf("#");return 0>b?a:a.substr(0,b)}function z(a){for(;a&&"A"!=a.nodeName;)a=a.parentNode;return a}function A(a){var b=e.protocol+"//"+e.host;if(!(b=a.target||a.hasAttribute("download")||0!=a.href.indexOf(b+"/")||-1+new Date-500||(a=z(a.target))&&A(a)&&x(a.href)}function N(a){G>+new Date-500||(a=z(a.target))&&A(a)&&(a.addEventListener("mouseout",T),H?(O=a.href,l=setTimeout(x,H)):x(a.href))}function U(a){G=+new Date;(a=z(a.target))&&A(a)&&(D?a.removeEventListener("mousedown", +M):a.removeEventListener("mouseover",N),x(a.href))}function V(a){var b=z(a.target);!b||!A(b)||1p.readyState)&&0!=p.status){q.ready=+new Date-q.start;if(p.getResponseHeader("Content-Type").match(/\/(x|ht|xht)ml/)){var a=d.implementation.createHTMLDocument("");a.documentElement.innerHTML=p.responseText.replace(//gi,"");y=a.title; +u=a.body;var b=t("receive",r,u,y);b&&("body"in b&&(u=b.body),"title"in b&&(y=b.title));b=w(r);h[b]={body:u,title:y,scrollY:b in h?h[b].scrollY:0};for(var a=a.head.children,b=0,c,g=a.length-1;0<=g;g--)if(c=a[g],c.hasAttribute("data-instant-track")){c=c.getAttribute("href")||c.getAttribute("src")||c.innerHTML;for(var e=E.length-1;0<=e;e--)E[e]==c&&b++}b!=E.length&&(F=!0)}else F=!0;m&&(m=!1,P(r))}}function L(a){d.body.addEventListener("touchstart",U,!0);D?d.body.addEventListener("mousedown",M,!0):d.body.addEventListener("mouseover", +N,!0);d.body.addEventListener("click",V,!0);if(!a){a=d.body.getElementsByTagName("script");var b,c,g,e;i=0;for(j=a.length;i+new Date-(q.start+q.display)||(l&&(clearTimeout(l),l=!1),a||(a=O),v&&(a==r||m))||(v=!0,m=!1,r=a,F=u=!1,q={start:+new Date},t("fetch"), +p.open("GET",a),p.send())}function P(a){"display"in q||(q.display=+new Date-q.start);l||!v?l&&r&&r!=a?e.href=a:(x(a),C.start(0,!0),t("wait"),m=!0):m?e.href=a:F?e.href=r:u?(h[k].scrollY=pageYOffset,m=v=!1,K(y,u,r)):(C.start(0,!0),t("wait"),m=!0)}var I=navigator.userAgent,S=-1b;b++)a[b]+"Transform"in h.style&&(k=a[b]+"Transform");var c="transition";if(!(c in h.style))for(b=0;3>b;b++)a[b]+"Transition"in +h.style&&(c="-"+a[b].toLowerCase()+"-"+c);a=d.createElement("style");a.innerHTML="#instantclick{position:"+(Q?"absolute":"fixed")+";top:0;left:0;width:100%;pointer-events:none;z-index:2147483647;"+c+":opacity .25s .1s}.instantclick-bar{background:#29d;width:100%;margin-left:-100%;height:2px;"+c+":all .25s}";d.head.appendChild(a);Q&&(m(),addEventListener("resize",m),addEventListener("scroll",m))},start:a,done:e}}(),R="pushState"in history&&(!I.match("Android")||I.match("Chrome/"))&&"file:"!=e.protocol; +return{supported:R,init:function(){if(!k)if(R){for(var a=arguments.length-1;0<=a;a--){var b=arguments[a];!0===b?J=!0:"mousedown"==b?D=!0:"number"==typeof b&&(H=b)}k=w(e.href);h[k]={body:d.body,title:d.title,scrollY:pageYOffset};for(var b=d.head.children,c,a=b.length-1;0<=a;a--)c=b[a],c.hasAttribute("data-instant-track")&&(c=c.getAttribute("href")||c.getAttribute("src")||c.innerHTML,E.push(c));p=new XMLHttpRequest;p.addEventListener("readystatechange",W);L(!0);C.init();t("change",!0);addEventListener("popstate", +function(){var a=w(e.href);a!=k&&(a in h?(h[k].scrollY=pageYOffset,k=a,K(h[a].title,h[a].body,!1,h[a].scrollY)):e.href=e.href)})}else t("change",!0)},on:function(a,b){B[a].push(b)}}}(document,location);