diff options
| author | Andrew Lee <alee14498@gmail.com> | 2019-10-27 09:57:37 -0400 |
|---|---|---|
| committer | Andrew Lee <alee14498@gmail.com> | 2019-10-27 09:57:37 -0400 |
| commit | e62e7bb6b14555c9bbe5d40d217103984f4f80e6 (patch) | |
| tree | 21887d847dccacb47644eb6f74ce31326172303c /assets/js/plugins | |
| parent | 716ea6ed2b64c921a799d872a07bfbd53b2a3e58 (diff) | |
| download | pokeworld-website-e62e7bb6b14555c9bbe5d40d217103984f4f80e6.tar.gz pokeworld-website-e62e7bb6b14555c9bbe5d40d217103984f4f80e6.tar.bz2 pokeworld-website-e62e7bb6b14555c9bbe5d40d217103984f4f80e6.zip | |
Rewrite progress
Diffstat (limited to 'assets/js/plugins')
| -rw-r--r-- | assets/js/plugins/gumshoe.js | 484 | ||||
| -rw-r--r-- | assets/js/plugins/jquery.ba-throttle-debounce.js | 252 | ||||
| -rw-r--r-- | assets/js/plugins/jquery.fitvids.js | 82 | ||||
| -rw-r--r-- | assets/js/plugins/jquery.greedy-navigation.js | 83 | ||||
| -rw-r--r-- | assets/js/plugins/jquery.magnific-popup.js | 1860 | ||||
| -rw-r--r-- | assets/js/plugins/smooth-scroll.js | 632 |
6 files changed, 3393 insertions, 0 deletions
diff --git a/assets/js/plugins/gumshoe.js b/assets/js/plugins/gumshoe.js new file mode 100644 index 0000000..713b6eb --- /dev/null +++ b/assets/js/plugins/gumshoe.js @@ -0,0 +1,484 @@ +/*! + * gumshoejs v5.1.1 + * A simple, framework-agnostic scrollspy script. + * (c) 2019 Chris Ferdinandi + * MIT License + * http://github.com/cferdinandi/gumshoe + */ + +(function (root, factory) { + if ( typeof define === 'function' && define.amd ) { + define([], (function () { + return factory(root); + })); + } else if ( typeof exports === 'object' ) { + module.exports = factory(root); + } else { + root.Gumshoe = factory(root); + } +})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this, (function (window) { + + 'use strict'; + + // + // Defaults + // + + var defaults = { + + // Active classes + navClass: 'active', + contentClass: 'active', + + // Nested navigation + nested: false, + nestedClass: 'active', + + // Offset & reflow + offset: 0, + reflow: false, + + // Event support + events: true + + }; + + + // + // Methods + // + + /** + * Merge two or more objects together. + * @param {Object} objects The objects to merge together + * @returns {Object} Merged values of defaults and options + */ + var extend = function () { + var merged = {}; + Array.prototype.forEach.call(arguments, (function (obj) { + for (var key in obj) { + if (!obj.hasOwnProperty(key)) return; + merged[key] = obj[key]; + } + })); + return merged; + }; + + /** + * Emit a custom event + * @param {String} type The event type + * @param {Node} elem The element to attach the event to + * @param {Object} detail Any details to pass along with the event + */ + var emitEvent = function (type, elem, detail) { + + // Make sure events are enabled + if (!detail.settings.events) return; + + // Create a new event + var event = new CustomEvent(type, { + bubbles: true, + cancelable: true, + detail: detail + }); + + // Dispatch the event + elem.dispatchEvent(event); + + }; + + /** + * Get an element's distance from the top of the Document. + * @param {Node} elem The element + * @return {Number} Distance from the top in pixels + */ + var getOffsetTop = function (elem) { + var location = 0; + if (elem.offsetParent) { + while (elem) { + location += elem.offsetTop; + elem = elem.offsetParent; + } + } + return location >= 0 ? location : 0; + }; + + /** + * Sort content from first to last in the DOM + * @param {Array} contents The content areas + */ + var sortContents = function (contents) { + if(contents) { + contents.sort((function (item1, item2) { + var offset1 = getOffsetTop(item1.content); + var offset2 = getOffsetTop(item2.content); + if (offset1 < offset2) return -1; + return 1; + })); + } + }; + + /** + * Get the offset to use for calculating position + * @param {Object} settings The settings for this instantiation + * @return {Float} The number of pixels to offset the calculations + */ + var getOffset = function (settings) { + + // if the offset is a function run it + if (typeof settings.offset === 'function') { + return parseFloat(settings.offset()); + } + + // Otherwise, return it as-is + return parseFloat(settings.offset); + + }; + + /** + * Get the document element's height + * @private + * @returns {Number} + */ + var getDocumentHeight = function () { + return Math.max( + document.body.scrollHeight, document.documentElement.scrollHeight, + document.body.offsetHeight, document.documentElement.offsetHeight, + document.body.clientHeight, document.documentElement.clientHeight + ); + }; + + /** + * Determine if an element is in view + * @param {Node} elem The element + * @param {Object} settings The settings for this instantiation + * @param {Boolean} bottom If true, check if element is above bottom of viewport instead + * @return {Boolean} Returns true if element is in the viewport + */ + var isInView = function (elem, settings, bottom) { + var bounds = elem.getBoundingClientRect(); + var offset = getOffset(settings); + if (bottom) { + return parseInt(bounds.bottom, 10) < (window.innerHeight || document.documentElement.clientHeight); + } + return parseInt(bounds.top, 10) <= offset; + }; + + /** + * Check if at the bottom of the viewport + * @return {Boolean} If true, page is at the bottom of the viewport + */ + var isAtBottom = function () { + if (window.innerHeight + window.pageYOffset >= getDocumentHeight()) return true; + return false; + }; + + /** + * Check if the last item should be used (even if not at the top of the page) + * @param {Object} item The last item + * @param {Object} settings The settings for this instantiation + * @return {Boolean} If true, use the last item + */ + var useLastItem = function (item, settings) { + if (isAtBottom() && isInView(item.content, settings, true)) return true; + return false; + }; + + /** + * Get the active content + * @param {Array} contents The content areas + * @param {Object} settings The settings for this instantiation + * @return {Object} The content area and matching navigation link + */ + var getActive = function (contents, settings) { + var last = contents[contents.length-1]; + if (useLastItem(last, settings)) return last; + for (var i = contents.length - 1; i >= 0; i--) { + if (isInView(contents[i].content, settings)) return contents[i]; + } + }; + + /** + * Deactivate parent navs in a nested navigation + * @param {Node} nav The starting navigation element + * @param {Object} settings The settings for this instantiation + */ + var deactivateNested = function (nav, settings) { + + // If nesting isn't activated, bail + if (!settings.nested) return; + + // Get the parent navigation + var li = nav.parentNode.closest('li'); + if (!li) return; + + // Remove the active class + li.classList.remove(settings.nestedClass); + + // Apply recursively to any parent navigation elements + deactivateNested(li, settings); + + }; + + /** + * Deactivate a nav and content area + * @param {Object} items The nav item and content to deactivate + * @param {Object} settings The settings for this instantiation + */ + var deactivate = function (items, settings) { + + // Make sure their are items to deactivate + if (!items) return; + + // Get the parent list item + var li = items.nav.closest('li'); + if (!li) return; + + // Remove the active class from the nav and content + li.classList.remove(settings.navClass); + items.content.classList.remove(settings.contentClass); + + // Deactivate any parent navs in a nested navigation + deactivateNested(li, settings); + + // Emit a custom event + emitEvent('gumshoeDeactivate', li, { + link: items.nav, + content: items.content, + settings: settings + }); + + }; + + + /** + * Activate parent navs in a nested navigation + * @param {Node} nav The starting navigation element + * @param {Object} settings The settings for this instantiation + */ + var activateNested = function (nav, settings) { + + // If nesting isn't activated, bail + if (!settings.nested) return; + + // Get the parent navigation + var li = nav.parentNode.closest('li'); + if (!li) return; + + // Add the active class + li.classList.add(settings.nestedClass); + + // Apply recursively to any parent navigation elements + activateNested(li, settings); + + }; + + /** + * Activate a nav and content area + * @param {Object} items The nav item and content to activate + * @param {Object} settings The settings for this instantiation + */ + var activate = function (items, settings) { + + // Make sure their are items to activate + if (!items) return; + + // Get the parent list item + var li = items.nav.closest('li'); + if (!li) return; + + // Add the active class to the nav and content + li.classList.add(settings.navClass); + items.content.classList.add(settings.contentClass); + + // Activate any parent navs in a nested navigation + activateNested(li, settings); + + // Emit a custom event + emitEvent('gumshoeActivate', li, { + link: items.nav, + content: items.content, + settings: settings + }); + + }; + + /** + * Create the Constructor object + * @param {String} selector The selector to use for navigation items + * @param {Object} options User options and settings + */ + var Constructor = function (selector, options) { + + // + // Variables + // + + var publicAPIs = {}; + var navItems, contents, current, timeout, settings; + + + // + // Methods + // + + /** + * Set variables from DOM elements + */ + publicAPIs.setup = function () { + + // Get all nav items + navItems = document.querySelectorAll(selector); + + // Create contents array + contents = []; + + // Loop through each item, get it's matching content, and push to the array + Array.prototype.forEach.call(navItems, (function (item) { + + // Get the content for the nav item + var content = document.getElementById(decodeURIComponent(item.hash.substr(1))); + if (!content) return; + + // Push to the contents array + contents.push({ + nav: item, + content: content + }); + + })); + + // Sort contents by the order they appear in the DOM + sortContents(contents); + + }; + + /** + * Detect which content is currently active + */ + publicAPIs.detect = function () { + + // Get the active content + var active = getActive(contents, settings); + + // if there's no active content, deactivate and bail + if (!active) { + if (current) { + deactivate(current, settings); + current = null; + } + return; + } + + // If the active content is the one currently active, do nothing + if (current && active.content === current.content) return; + + // Deactivate the current content and activate the new content + deactivate(current, settings); + activate(active, settings); + + // Update the currently active content + current = active; + + }; + + /** + * Detect the active content on scroll + * Debounced for performance + */ + var scrollHandler = function (event) { + + // If there's a timer, cancel it + if (timeout) { + window.cancelAnimationFrame(timeout); + } + + // Setup debounce callback + timeout = window.requestAnimationFrame(publicAPIs.detect); + + }; + + /** + * Update content sorting on resize + * Debounced for performance + */ + var resizeHandler = function (event) { + + // If there's a timer, cancel it + if (timeout) { + window.cancelAnimationFrame(timeout); + } + + // Setup debounce callback + timeout = window.requestAnimationFrame((function () { + sortContents(contents); + publicAPIs.detect(); + })); + + }; + + /** + * Destroy the current instantiation + */ + publicAPIs.destroy = function () { + + // Undo DOM changes + if (current) { + deactivate(current, settings); + } + + // Remove event listeners + window.removeEventListener('scroll', scrollHandler, false); + if (settings.reflow) { + window.removeEventListener('resize', resizeHandler, false); + } + + // Reset variables + contents = null; + navItems = null; + current = null; + timeout = null; + settings = null; + + }; + + /** + * Initialize the current instantiation + */ + var init = function () { + + // Merge user options into defaults + settings = extend(defaults, options || {}); + + // Setup variables based on the current DOM + publicAPIs.setup(); + + // Find the currently active content + publicAPIs.detect(); + + // Setup event listeners + window.addEventListener('scroll', scrollHandler, false); + if (settings.reflow) { + window.addEventListener('resize', resizeHandler, false); + } + + }; + + + // + // Initialize and return the public APIs + // + + init(); + return publicAPIs; + + }; + + + // + // Return the Constructor + // + + return Constructor; + +}));
\ No newline at end of file diff --git a/assets/js/plugins/jquery.ba-throttle-debounce.js b/assets/js/plugins/jquery.ba-throttle-debounce.js new file mode 100644 index 0000000..fa30bdf --- /dev/null +++ b/assets/js/plugins/jquery.ba-throttle-debounce.js @@ -0,0 +1,252 @@ +/*! + * jQuery throttle / debounce - v1.1 - 3/7/2010 + * http://benalman.com/projects/jquery-throttle-debounce-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Script: jQuery throttle / debounce: Sometimes, less is more! +// +// *Version: 1.1, Last updated: 3/7/2010* +// +// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/ +// GitHub - http://github.com/cowboy/jquery-throttle-debounce/ +// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js +// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. +// +// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/ +// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - none, 1.3.2, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1. +// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/ +// +// About: Release History +// +// 1.1 - (3/7/2010) Fixed a bug in <jQuery.throttle> where trailing callbacks +// executed later than they should. Reworked a fair amount of internal +// logic as well. +// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over +// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the +// no_trailing throttle parameter and debounce functionality. +// +// Topic: Note for non-jQuery users +// +// jQuery isn't actually required for this plugin, because nothing internal +// uses any jQuery methods or properties. jQuery is just used as a namespace +// under which these methods can exist. +// +// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist +// when this plugin is loaded, the method described below will be created in +// the `Cowboy` namespace. Usage will be exactly the same, but instead of +// $.method() or jQuery.method(), you'll need to use Cowboy.method(). + +(function(window,undefined){ + '$:nomunge'; // Used by YUI compressor. + + // Since jQuery really isn't required for this plugin, use `jQuery` as the + // namespace only if it already exists, otherwise use the `Cowboy` namespace, + // creating it if necessary. + var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ), + + // Internal method reference. + jq_throttle; + + // Method: jQuery.throttle + // + // Throttle execution of a function. Especially useful for rate limiting + // execution of handlers on events like resize and scroll. If you want to + // rate-limit execution of a function to a single time, see the + // <jQuery.debounce> method. + // + // In this visualization, | is a throttled-function call and X is the actual + // callback execution: + // + // > Throttled with `no_trailing` specified as false or unspecified: + // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| + // > X X X X X X X X X X X X + // > + // > Throttled with `no_trailing` specified as true: + // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| + // > X X X X X X X X X X + // + // Usage: + // + // > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback ); + // > + // > jQuery('selector').bind( 'someevent', throttled ); + // > jQuery('selector').unbind( 'someevent', throttled ); + // + // This also works in jQuery 1.4+: + // + // > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) ); + // > jQuery('selector').unbind( 'someevent', callback ); + // + // Arguments: + // + // delay - (Number) A zero-or-greater delay in milliseconds. For event + // callbacks, values around 100 or 250 (or even higher) are most useful. + // no_trailing - (Boolean) Optional, defaults to false. If no_trailing is + // true, callback will only execute every `delay` milliseconds while the + // throttled-function is being called. If no_trailing is false or + // unspecified, callback will be executed one final time after the last + // throttled-function call. (After the throttled-function has not been + // called for `delay` milliseconds, the internal counter is reset) + // callback - (Function) A function to be executed after delay milliseconds. + // The `this` context and all arguments are passed through, as-is, to + // `callback` when the throttled-function is executed. + // + // Returns: + // + // (Function) A new, throttled, function. + + $.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) { + // After wrapper has stopped being called, this timeout ensures that + // `callback` is executed at the proper times in `throttle` and `end` + // debounce modes. + var timeout_id, + + // Keep track of the last time `callback` was executed. + last_exec = 0; + + // `no_trailing` defaults to falsy. + if ( typeof no_trailing !== 'boolean' ) { + debounce_mode = callback; + callback = no_trailing; + no_trailing = undefined; + } + + // The `wrapper` function encapsulates all of the throttling / debouncing + // functionality and when executed will limit the rate at which `callback` + // is executed. + function wrapper() { + var that = this, + elapsed = +new Date() - last_exec, + args = arguments; + + // Execute `callback` and update the `last_exec` timestamp. + function exec() { + last_exec = +new Date(); + callback.apply( that, args ); + }; + + // If `debounce_mode` is true (at_begin) this is used to clear the flag + // to allow future `callback` executions. + function clear() { + timeout_id = undefined; + }; + + if ( debounce_mode && !timeout_id ) { + // Since `wrapper` is being called for the first time and + // `debounce_mode` is true (at_begin), execute `callback`. + exec(); + } + + // Clear any existing timeout. + timeout_id && clearTimeout( timeout_id ); + + if ( debounce_mode === undefined && elapsed > delay ) { + // In throttle mode, if `delay` time has been exceeded, execute + // `callback`. + exec(); + + } else if ( no_trailing !== true ) { + // In trailing throttle mode, since `delay` time has not been + // exceeded, schedule `callback` to execute `delay` ms after most + // recent execution. + // + // If `debounce_mode` is true (at_begin), schedule `clear` to execute + // after `delay` ms. + // + // If `debounce_mode` is false (at end), schedule `callback` to + // execute after `delay` ms. + timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay ); + } + }; + + // Set the guid of `wrapper` function to the same of original callback, so + // it can be removed in jQuery 1.4+ .unbind or .die by using the original + // callback as a reference. + if ( $.guid ) { + wrapper.guid = callback.guid = callback.guid || $.guid++; + } + + // Return the wrapper function. + return wrapper; + }; + + // Method: jQuery.debounce + // + // Debounce execution of a function. Debouncing, unlike throttling, + // guarantees that a function is only executed a single time, either at the + // very beginning of a series of calls, or at the very end. If you want to + // simply rate-limit execution of a function, see the <jQuery.throttle> + // method. + // + // In this visualization, | is a debounced-function call and X is the actual + // callback execution: + // + // > Debounced with `at_begin` specified as false or unspecified: + // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| + // > X X + // > + // > Debounced with `at_begin` specified as true: + // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| + // > X X + // + // Usage: + // + // > var debounced = jQuery.debounce( delay, [ at_begin, ] callback ); + // > + // > jQuery('selector').bind( 'someevent', debounced ); + // > jQuery('selector').unbind( 'someevent', debounced ); + // + // This also works in jQuery 1.4+: + // + // > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) ); + // > jQuery('selector').unbind( 'someevent', callback ); + // + // Arguments: + // + // delay - (Number) A zero-or-greater delay in milliseconds. For event + // callbacks, values around 100 or 250 (or even higher) are most useful. + // at_begin - (Boolean) Optional, defaults to false. If at_begin is false or + // unspecified, callback will only be executed `delay` milliseconds after + // the last debounced-function call. If at_begin is true, callback will be + // executed only at the first debounced-function call. (After the + // throttled-function has not been called for `delay` milliseconds, the + // internal counter is reset) + // callback - (Function) A function to be executed after delay milliseconds. + // The `this` context and all arguments are passed through, as-is, to + // `callback` when the debounced-function is executed. + // + // Returns: + // + // (Function) A new, debounced, function. + + $.debounce = function( delay, at_begin, callback ) { + return callback === undefined + ? jq_throttle( delay, at_begin, false ) + : jq_throttle( delay, callback, at_begin !== false ); + }; + +})(this); diff --git a/assets/js/plugins/jquery.fitvids.js b/assets/js/plugins/jquery.fitvids.js new file mode 100644 index 0000000..5c2f85c --- /dev/null +++ b/assets/js/plugins/jquery.fitvids.js @@ -0,0 +1,82 @@ +/*jshint browser:true */ +/*! +* FitVids 1.1 +* +* Copyright 2013, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com +* Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ +* Released under the WTFPL license - http://sam.zoy.org/wtfpl/ +* +*/ + +;(function( $ ){ + + 'use strict'; + + $.fn.fitVids = function( options ) { + var settings = { + customSelector: null, + ignore: null + }; + + if(!document.getElementById('fit-vids-style')) { + // appendStyles: https://github.com/toddmotto/fluidvids/blob/master/dist/fluidvids.js + var head = document.head || document.getElementsByTagName('head')[0]; + var css = '.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}'; + var div = document.createElement("div"); + div.innerHTML = '<p>x</p><style id="fit-vids-style">' + css + '</style>'; + head.appendChild(div.childNodes[1]); + } + + if ( options ) { + $.extend( settings, options ); + } + + return this.each(function(){ + var selectors = [ + 'iframe[src*="player.vimeo.com"]', + 'iframe[src*="youtube.com"]', + 'iframe[src*="youtube-nocookie.com"]', + 'iframe[src*="kickstarter.com"][src*="video.html"]', + 'object', + 'embed' + ]; + + if (settings.customSelector) { + selectors.push(settings.customSelector); + } + + var ignoreList = '.fitvidsignore'; + + if(settings.ignore) { + ignoreList = ignoreList + ', ' + settings.ignore; + } + + var $allVideos = $(this).find(selectors.join(',')); + $allVideos = $allVideos.not('object object'); // SwfObj conflict patch + $allVideos = $allVideos.not(ignoreList); // Disable FitVids on this video. + + $allVideos.each(function(count){ + var $this = $(this); + if($this.parents(ignoreList).length > 0) { + return; // Disable FitVids on this video. + } + if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } + if ((!$this.css('height') && !$this.css('width')) && (isNaN($this.attr('height')) || isNaN($this.attr('width')))) + { + $this.attr('height', 9); + $this.attr('width', 16); + } + var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), + width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), + aspectRatio = height / width; + if(!$this.attr('id')){ + var videoID = 'fitvid' + count; + $this.attr('id', videoID); + } + $this.wrap('<div class="fluid-width-video-wrapper"></div>').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+'%'); + $this.removeAttr('height').removeAttr('width'); + }); + }); + }; +// Works with either jQuery or Zepto +})( window.jQuery || window.Zepto );
\ No newline at end of file diff --git a/assets/js/plugins/jquery.greedy-navigation.js b/assets/js/plugins/jquery.greedy-navigation.js new file mode 100644 index 0000000..3eabccd --- /dev/null +++ b/assets/js/plugins/jquery.greedy-navigation.js @@ -0,0 +1,83 @@ +/* +GreedyNav.js - https://github.com/lukejacksonn/GreedyNav +Licensed under the MIT license - http://opensource.org/licenses/MIT +Copyright (c) 2015 Luke Jackson +*/ + +$(document).ready(function() { + var $btn = $("nav.greedy-nav .greedy-nav__toggle"); + var $vlinks = $("nav.greedy-nav .visible-links"); + var $hlinks = $("nav.greedy-nav .hidden-links"); + + var numOfItems = 0; + var totalSpace = 0; + var closingTime = 1000; + var breakWidths = []; + + // Get initial state + $vlinks.children().outerWidth(function(i, w) { + totalSpace += w; + numOfItems += 1; + breakWidths.push(totalSpace); + }); + + var availableSpace, numOfVisibleItems, requiredSpace, timer; + + function check() { + // Get instant state + availableSpace = $vlinks.width() - $btn.width(); + numOfVisibleItems = $vlinks.children().length; + requiredSpace = breakWidths[numOfVisibleItems - 1]; + + // There is not enough space + if (requiredSpace > availableSpace) { + $vlinks + .children() + .last() + .prependTo($hlinks); + numOfVisibleItems -= 1; + check(); + // There is more than enough space + } else if (availableSpace > breakWidths[numOfVisibleItems]) { + $hlinks + .children() + .first() + .appendTo($vlinks); + numOfVisibleItems += 1; + check(); + } + // Update the button accordingly + $btn.attr("count", numOfItems - numOfVisibleItems); + if (numOfVisibleItems === numOfItems) { + $btn.addClass("hidden"); + } else { + $btn.removeClass("hidden"); + } + } + + // Window listeners + $(window).resize(function() { + check(); + }); + + $btn.on("click", function() { + $hlinks.toggleClass("hidden"); + $(this).toggleClass("close"); + clearTimeout(timer); + }); + + $hlinks + .on("mouseleave", function() { + // Mouse has left, start the timer + timer = setTimeout(function() { + $hlinks.addClass("hidden"); + $btn.toggleClass("close"); + }, closingTime); + }) + .on("mouseenter", function() { + // Mouse is back, cancel the timer + clearTimeout(timer); + }); + + check(); +}); diff --git a/assets/js/plugins/jquery.magnific-popup.js b/assets/js/plugins/jquery.magnific-popup.js new file mode 100644 index 0000000..7d1d197 --- /dev/null +++ b/assets/js/plugins/jquery.magnific-popup.js @@ -0,0 +1,1860 @@ +/*! Magnific Popup - v1.1.0 - 2016-02-20 +* http://dimsemenov.com/plugins/magnific-popup/ +* Copyright (c) 2016 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, $.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.isLowIE = mfp.isIE8 = document.all && !document.addEventListener; + 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 = $.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 = ''; + 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(); + } + }); + + 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.append( _getCloseBtn() ); + } 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) { + 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); + + 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); + setTimeout(function() { + 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.st.autoFocusLast && 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.content.find('.mfp-close').length) { + mfp.content.append(_getCloseBtn()); + } + } else { + mfp.content = newContent; + } + } else { + mfp.content = ''; + } + + _mfpTrigger(BEFORE_APPEND_EVENT); + mfp.container.addClass('mfp-'+type+'-holder'); + + 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 || e.altKey || e.shiftKey ) ) { + return; + } + + var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn; + + if(disableOn) { + if($.isFunction(disableOn)) { + 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') || (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).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>').attr('src', value).attr('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">×</button>', + + tClose: 'Close (Esc)', + + tLoading: 'Loading...', + + autoFocusLast: true + + } + }; + + + + $.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; + }; + + /*>>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($.isFunction(src)) { + 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'; + + 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); + + arrowLeft.click(function() { + mfp.prev(); + }); + arrowRight.click(function() { + mfp.next(); + }); + + 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); + 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; + } + } + }); + + /*>>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*/ + _checkInstance(); }));
\ No newline at end of file diff --git a/assets/js/plugins/smooth-scroll.js b/assets/js/plugins/smooth-scroll.js new file mode 100644 index 0000000..e0cf796 --- /dev/null +++ b/assets/js/plugins/smooth-scroll.js @@ -0,0 +1,632 @@ +/*! + * smooth-scroll v15.2.1 + * Animate scrolling to anchor links + * (c) 2019 Chris Ferdinandi + * MIT License + * http://github.com/cferdinandi/smooth-scroll + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], (function () { + return factory(root); + })); + } else if (typeof exports === 'object') { + module.exports = factory(root); + } else { + root.SmoothScroll = factory(root); + } +})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this, (function (window) { + + 'use strict'; + + // + // Default settings + // + + var defaults = { + + // Selectors + ignore: '[data-scroll-ignore]', + header: null, + topOnEmptyHash: true, + + // Speed & Duration + speed: 500, + speedAsDuration: false, + durationMax: null, + durationMin: null, + clip: true, + offset: 0, + + // Easing + easing: 'easeInOutCubic', + customEasing: null, + + // History + updateURL: true, + popstate: true, + + // Custom Events + emitEvents: true + + }; + + + // + // Utility Methods + // + + /** + * Check if browser supports required methods + * @return {Boolean} Returns true if all required methods are supported + */ + var supports = function () { + return ( + 'querySelector' in document && + 'addEventListener' in window && + 'requestAnimationFrame' in window && + 'closest' in window.Element.prototype + ); + }; + + /** + * Merge two or more objects together. + * @param {Object} objects The objects to merge together + * @returns {Object} Merged values of defaults and options + */ + var extend = function () { + var merged = {}; + Array.prototype.forEach.call(arguments, (function (obj) { + for (var key in obj) { + if (!obj.hasOwnProperty(key)) return; + merged[key] = obj[key]; + } + })); + return merged; + }; + + /** + * Check to see if user prefers reduced motion + * @param {Object} settings Script settings + */ + var reduceMotion = function (settings) { + if ('matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches) { + return true; + } + return false; + }; + + /** + * Get the height of an element. + * @param {Node} elem The element to get the height of + * @return {Number} The element's height in pixels + */ + var getHeight = function (elem) { + return parseInt(window.getComputedStyle(elem).height, 10); + }; + + /** + * Escape special characters for use with querySelector + * @author Mathias Bynens + * @link https://github.com/mathiasbynens/CSS.escape + * @param {String} id The anchor ID to escape + */ + var escapeCharacters = function (id) { + + // Remove leading hash + if (id.charAt(0) === '#') { + id = id.substr(1); + } + + var string = String(id); + var length = string.length; + var index = -1; + var codeUnit; + var result = ''; + var firstCodeUnit = string.charCodeAt(0); + while (++index < length) { + codeUnit = string.charCodeAt(index); + // Note: there’s no need to special-case astral symbols, surrogate + // pairs, or lone surrogates. + + // If the character is NULL (U+0000), then throw an + // `InvalidCharacterError` exception and terminate these steps. + if (codeUnit === 0x0000) { + throw new InvalidCharacterError( + 'Invalid character: the input contains U+0000.' + ); + } + + if ( + // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is + // U+007F, […] + (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || + // If the character is the first character and is in the range [0-9] + // (U+0030 to U+0039), […] + (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || + // If the character is the second character and is in the range [0-9] + // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] + ( + index === 1 && + codeUnit >= 0x0030 && codeUnit <= 0x0039 && + firstCodeUnit === 0x002D + ) + ) { + // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point + result += '\\' + codeUnit.toString(16) + ' '; + continue; + } + + // If the character is not handled by one of the above rules and is + // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or + // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to + // U+005A), or [a-z] (U+0061 to U+007A), […] + if ( + codeUnit >= 0x0080 || + codeUnit === 0x002D || + codeUnit === 0x005F || + codeUnit >= 0x0030 && codeUnit <= 0x0039 || + codeUnit >= 0x0041 && codeUnit <= 0x005A || + codeUnit >= 0x0061 && codeUnit <= 0x007A + ) { + // the character itself + result += string.charAt(index); + continue; + } + + // Otherwise, the escaped character. + // http://dev.w3.org/csswg/cssom/#escape-a-character + result += '\\' + string.charAt(index); + + } + + // Return sanitized hash + return '#' + result; + + }; + + /** + * Calculate the easing pattern + * @link https://gist.github.com/gre/1650294 + * @param {String} type Easing pattern + * @param {Number} time Time animation should take to complete + * @returns {Number} + */ + var easingPattern = function (settings, time) { + var pattern; + + // Default Easing Patterns + if (settings.easing === 'easeInQuad') pattern = time * time; // accelerating from zero velocity + if (settings.easing === 'easeOutQuad') pattern = time * (2 - time); // decelerating to zero velocity + if (settings.easing === 'easeInOutQuad') pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration + if (settings.easing === 'easeInCubic') pattern = time * time * time; // accelerating from zero velocity + if (settings.easing === 'easeOutCubic') pattern = (--time) * time * time + 1; // decelerating to zero velocity + if (settings.easing === 'easeInOutCubic') pattern = time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration + if (settings.easing === 'easeInQuart') pattern = time * time * time * time; // accelerating from zero velocity + if (settings.easing === 'easeOutQuart') pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity + if (settings.easing === 'easeInOutQuart') pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration + if (settings.easing === 'easeInQuint') pattern = time * time * time * time * time; // accelerating from zero velocity + if (settings.easing === 'easeOutQuint') pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity + if (settings.easing === 'easeInOutQuint') pattern = time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration + + // Custom Easing Patterns + if (!!settings.customEasing) pattern = settings.customEasing(time); + + return pattern || time; // no easing, no acceleration + }; + + /** + * Determine the document's height + * @returns {Number} + */ + var getDocumentHeight = function () { + return Math.max( + document.body.scrollHeight, document.documentElement.scrollHeight, + document.body.offsetHeight, document.documentElement.offsetHeight, + document.body.clientHeight, document.documentElement.clientHeight + ); + }; + + /** + * Calculate how far to scroll + * Clip support added by robjtede - https://github.com/cferdinandi/smooth-scroll/issues/405 + * @param {Element} anchor The anchor element to scroll to + * @param {Number} headerHeight Height of a fixed header, if any + * @param {Number} offset Number of pixels by which to offset scroll + * @param {Boolean} clip If true, adjust scroll distance to prevent abrupt stops near the bottom of the page + * @returns {Number} + */ + var getEndLocation = function (anchor, headerHeight, offset, clip) { + var location = 0; + if (anchor.offsetParent) { + do { + location += anchor.offsetTop; + anchor = anchor.offsetParent; + } while (anchor); + } + location = Math.max(location - headerHeight - offset, 0); + if (clip) { + location = Math.min(location, getDocumentHeight() - window.innerHeight); + } + return location; + }; + + /** + * Get the height of the fixed header + * @param {Node} header The header + * @return {Number} The height of the header + */ + var getHeaderHeight = function (header) { + return !header ? 0 : (getHeight(header) + header.offsetTop); + }; + + /** + * Calculate the speed to use for the animation + * @param {Number} distance The distance to travel + * @param {Object} settings The plugin settings + * @return {Number} How fast to animate + */ + var getSpeed = function (distance, settings) { + var speed = settings.speedAsDuration ? settings.speed : Math.abs(distance / 1000 * settings.speed); + if (settings.durationMax && speed > settings.durationMax) return settings.durationMax; + if (settings.durationMin && speed < settings.durationMin) return settings.durationMin; + return parseInt(speed, 10); + }; + + var setHistory = function (options) { + + // Make sure this should run + if (!history.replaceState || !options.updateURL || history.state) return; + + // Get the hash to use + var hash = window.location.hash; + hash = hash ? hash : ''; + + // Set a default history + history.replaceState( + { + smoothScroll: JSON.stringify(options), + anchor: hash ? hash : window.pageYOffset + }, + document.title, + hash ? hash : window.location.href + ); + + }; + + /** + * Update the URL + * @param {Node} anchor The anchor that was scrolled to + * @param {Boolean} isNum If true, anchor is a number + * @param {Object} options Settings for Smooth Scroll + */ + var updateURL = function (anchor, isNum, options) { + + // Bail if the anchor is a number + if (isNum) return; + + // Verify that pushState is supported and the updateURL option is enabled + if (!history.pushState || !options.updateURL) return; + + // Update URL + history.pushState( + { + smoothScroll: JSON.stringify(options), + anchor: anchor.id + }, + document.title, + anchor === document.documentElement ? '#top' : '#' + anchor.id + ); + + }; + + /** + * Bring the anchored element into focus + * @param {Node} anchor The anchor element + * @param {Number} endLocation The end location to scroll to + * @param {Boolean} isNum If true, scroll is to a position rather than an element + */ + var adjustFocus = function (anchor, endLocation, isNum) { + + // Is scrolling to top of page, blur + if (anchor === 0) { + document.body.focus(); + } + + // Don't run if scrolling to a number on the page + if (isNum) return; + + // Otherwise, bring anchor element into focus + anchor.focus(); + if (document.activeElement !== anchor) { + anchor.setAttribute('tabindex', '-1'); + anchor.focus(); + anchor.style.outline = 'none'; + } + window.scrollTo(0 , endLocation); + + }; + + /** + * Emit a custom event + * @param {String} type The event type + * @param {Object} options The settings object + * @param {Node} anchor The anchor element + * @param {Node} toggle The toggle element + */ + var emitEvent = function (type, options, anchor, toggle) { + if (!options.emitEvents || typeof window.CustomEvent !== 'function') return; + var event = new CustomEvent(type, { + bubbles: true, + detail: { + anchor: anchor, + toggle: toggle + } + }); + document.dispatchEvent(event); + }; + + + // + // SmoothScroll Constructor + // + + var SmoothScroll = function (selector, options) { + + // + // Variables + // + + var smoothScroll = {}; // Object for public APIs + var settings, anchor, toggle, fixedHeader, eventTimeout, animationInterval; + + + // + // Methods + // + + /** + * Cancel a scroll-in-progress + */ + smoothScroll.cancelScroll = function (noEvent) { + cancelAnimationFrame(animationInterval); + animationInterval = null; + if (noEvent) return; + emitEvent('scrollCancel', settings); + }; + + /** + * Start/stop the scrolling animation + * @param {Node|Number} anchor The element or position to scroll to + * @param {Element} toggle The element that toggled the scroll event + * @param {Object} options + */ + smoothScroll.animateScroll = function (anchor, toggle, options) { + + // Cancel any in progress scrolls + smoothScroll.cancelScroll(); + + // Local settings + var _settings = extend(settings || defaults, options || {}); // Merge user options with defaults + + // Selectors and variables + var isNum = Object.prototype.toString.call(anchor) === '[object Number]' ? true : false; + var anchorElem = isNum || !anchor.tagName ? null : anchor; + if (!isNum && !anchorElem) return; + var startLocation = window.pageYOffset; // Current location on the page + if (_settings.header && !fixedHeader) { + // Get the fixed header if not already set + fixedHeader = document.querySelector(_settings.header); + } + var headerHeight = getHeaderHeight(fixedHeader); + var endLocation = isNum ? anchor : getEndLocation(anchorElem, headerHeight, parseInt((typeof _settings.offset === 'function' ? _settings.offset(anchor, toggle) : _settings.offset), 10), _settings.clip); // Location to scroll to + var distance = endLocation - startLocation; // distance to travel + var documentHeight = getDocumentHeight(); + var timeLapsed = 0; + var speed = getSpeed(distance, _settings); + var start, percentage, position; + + /** + * Stop the scroll animation when it reaches its target (or the bottom/top of page) + * @param {Number} position Current position on the page + * @param {Number} endLocation Scroll to location + * @param {Number} animationInterval How much to scroll on this loop + */ + var stopAnimateScroll = function (position, endLocation) { + + // Get the current location + var currentLocation = window.pageYOffset; + + // Check if the end location has been reached yet (or we've hit the end of the document) + if (position == endLocation || currentLocation == endLocation || ((startLocation < endLocation && window.innerHeight + currentLocation) >= documentHeight)) { + + // Clear the animation timer + smoothScroll.cancelScroll(true); + + // Bring the anchored element into focus + adjustFocus(anchor, endLocation, isNum); + + // Emit a custom event + emitEvent('scrollStop', _settings, anchor, toggle); + + // Reset start + start = null; + animationInterval = null; + + return true; + + } + }; + + /** + * Loop scrolling animation + */ + var loopAnimateScroll = function (timestamp) { + if (!start) { start = timestamp; } + timeLapsed += timestamp - start; + percentage = speed === 0 ? 0 : (timeLapsed / speed); + percentage = (percentage > 1) ? 1 : percentage; + position = startLocation + (distance * easingPattern(_settings, percentage)); + window.scrollTo(0, Math.floor(position)); + if (!stopAnimateScroll(position, endLocation)) { + animationInterval = window.requestAnimationFrame(loopAnimateScroll); + start = timestamp; + } + }; + + /** + * Reset position to fix weird iOS bug + * @link https://github.com/cferdinandi/smooth-scroll/issues/45 + */ + if (window.pageYOffset === 0) { + window.scrollTo(0, 0); + } + + // Update the URL + updateURL(anchor, isNum, _settings); + + // Emit a custom event + emitEvent('scrollStart', _settings, anchor, toggle); + + // Start scrolling animation + smoothScroll.cancelScroll(true); + window.requestAnimationFrame(loopAnimateScroll); + + }; + + /** + * If smooth scroll element clicked, animate scroll + */ + var clickHandler = function (event) { + + // Don't run if the user prefers reduced motion + if (reduceMotion(settings)) return; + + // Don't run if right-click or command/control + click + if (event.button !== 0 || event.metaKey || event.ctrlKey) return; + + // Check if event.target has closest() method + // By @totegi - https://github.com/cferdinandi/smooth-scroll/pull/401/ + if(!('closest' in event.target))return; + + // Check if a smooth scroll link was clicked + toggle = event.target.closest(selector); + if (!toggle || toggle.tagName.toLowerCase() !== 'a' || event.target.closest(settings.ignore)) return; + + // Only run if link is an anchor and points to the current page + if (toggle.hostname !== window.location.hostname || toggle.pathname !== window.location.pathname || !/#/.test(toggle.href)) return; + + // Get an escaped version of the hash + var hash = escapeCharacters(toggle.hash); + + // Get the anchored element + var anchor = settings.topOnEmptyHash && hash === '#' ? document.documentElement : document.querySelector(hash); + anchor = !anchor && hash === '#top' ? document.documentElement : anchor; + + // If anchored element exists, scroll to it + if (!anchor) return; + event.preventDefault(); + setHistory(settings); + smoothScroll.animateScroll(anchor, toggle); + + }; + + /** + * Animate scroll on popstate events + */ + var popstateHandler = function (event) { + + // Stop if history.state doesn't exist (ex. if clicking on a broken anchor link). + // fixes `Cannot read property 'smoothScroll' of null` error getting thrown. + if (history.state === null) return; + + // Only run if state is a popstate record for this instantiation + if (!history.state.smoothScroll || history.state.smoothScroll !== JSON.stringify(settings)) return; + + // Only run if state includes an anchor + + // if (!history.state.anchor && history.state.anchor !== 0) return; + + // Get the anchor + var anchor = history.state.anchor; + if (typeof anchor === 'string' && anchor) { + anchor = document.querySelector(escapeCharacters(history.state.anchor)); + if (!anchor) return; + } + + // Animate scroll to anchor link + smoothScroll.animateScroll(anchor, null, {updateURL: false}); + + }; + + /** + * Destroy the current initialization. + */ + smoothScroll.destroy = function () { + + // If plugin isn't already initialized, stop + if (!settings) return; + + // Remove event listeners + document.removeEventListener('click', clickHandler, false); + window.removeEventListener('popstate', popstateHandler, false); + + // Cancel any scrolls-in-progress + smoothScroll.cancelScroll(); + + // Reset variables + settings = null; + anchor = null; + toggle = null; + fixedHeader = null; + eventTimeout = null; + animationInterval = null; + + }; + + /** + * Initialize Smooth Scroll + * @param {Object} options User settings + */ + smoothScroll.init = function (options) { + + // feature test + if (!supports()) throw 'Smooth Scroll: This browser does not support the required JavaScript methods and browser APIs.'; + + // Destroy any existing initializations + smoothScroll.destroy(); + + // Selectors and variables + settings = extend(defaults, options || {}); // Merge user options with defaults + fixedHeader = settings.header ? document.querySelector(settings.header) : null; // Get the fixed header + + // When a toggle is clicked, run the click handler + document.addEventListener('click', clickHandler, false); + + // If updateURL and popState are enabled, listen for pop events + if (settings.updateURL && settings.popstate) { + window.addEventListener('popstate', popstateHandler, false); + } + + }; + + + // + // Initialize plugin + // + + smoothScroll.init(options); + + + // + // Public APIs + // + + return smoothScroll; + + }; + + return SmoothScroll; + +})); |
