text: /*!
   * gumshoejs v5.1.2
   * A simple, framework-agnostic scrollspy script.
   * (c) 2020 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,
  
  		// Selector for parent element that will receive active class.
  		// Set to false if active should be set on the link.
  		activeNavElement: 'li'
  
  	};
  
  
  	//
  	// 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() && item && 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];
  		}
  	};
  
  	/**
  	 * Return the element for the active class toggle
  	 * @param {Node} nav        The navigation element
  	 * @param {Object} settings The settings for this instantiation
  	 * @return {Node}           The element to receive 'active' class
  	 */
  	var getNodeToActivate = function (nav, settings) {
  		return settings.activeNavElement ? nav.closest(settings.activeNavElement) : nav;
  	}
  
  	/**
  	 * 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 || !nav.parentNode) return;
  
  		// Get the parent navigation
  		var activeEl = getNodeToActivate(nav.parentNode, settings);
  		if (!activeEl) return;
  
  		// Remove the active class
  		activeEl.classList.remove(settings.nestedClass);
  
  		// Apply recursively to any parent navigation elements
  		deactivateNested(activeEl, 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 there are items to deactivate
  		if (!items) return;
  
  		// Get the parent list item
  		var activeEl = getNodeToActivate(items.nav, settings);
  		if (!activeEl) return;
  
  		// Remove the active class from the nav and content
  		activeEl.classList.remove(settings.navClass);
  		items.content.classList.remove(settings.contentClass);
  
  		// Deactivate any parent navs in a nested navigation
  		deactivateNested(activeEl, settings);
  
  		// Emit a custom event
  		emitEvent('gumshoeDeactivate', activeEl, {
  			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 activeEl = getNodeToActivate(nav.parentNode, settings);
  		if (!activeEl) return;
  
  		// Add the active class
  		activeEl.classList.add(settings.nestedClass);
  
  		// Apply recursively to any parent navigation elements
  		activateNested(activeEl, 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 there are items to activate
  		if (!items) return;
  
  		// Get the parent list item
  		var activeEl = getNodeToActivate(items.nav, settings);
  		if (!activeEl) return;
  
  		// Add the active class to the nav and content
  		activeEl.classList.add(settings.navClass);
  		items.content.classList.add(settings.contentClass);
  
  		// Activate any parent navs in a nested navigation
  		activateNested(activeEl, settings);
  
  		// Emit a custom event
  		emitEvent('gumshoeActivate', activeEl, {
  			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;
  
  }));
  