/* Minification failed. Returning unminified contents.
(8725,73-74): run-time error JS1195: Expected expression: >
(8743,26-27): run-time error JS1195: Expected expression: )
(8743,27-28): run-time error JS1193: Expected ',' or ')': ;
(8760,18-19): run-time error JS1195: Expected expression: )
(8760,30-31): run-time error JS1004: Expected ';': )
(8764,36-37): run-time error JS1004: Expected ';': {
(8782,13-17): run-time error JS1034: Unmatched 'else'; no 'if' defined: else
(8782,43-44): run-time error JS1004: Expected ';': {
(8788,13-17): run-time error JS1034: Unmatched 'else'; no 'if' defined: else
(8793,13-14): run-time error JS1002: Syntax error: }
(8796,40-41): run-time error JS1004: Expected ';': {
(8800,5-6): run-time error JS1002: Syntax error: }
(8806,34-35): run-time error JS1195: Expected expression: )
(8806,36-37): run-time error JS1004: Expected ';': {
(9589,1-2): run-time error JS1002: Syntax error: }
(9589,37-38): run-time error JS1002: Syntax error: }
(9595,29-30): run-time error JS1195: Expected expression: )
(9595,31-32): run-time error JS1004: Expected ';': {
(9623,2-3): run-time error JS1195: Expected expression: )
(9630,29-30): run-time error JS1195: Expected expression: )
(9630,31-32): run-time error JS1004: Expected ';': {
(9632,2-3): run-time error JS1195: Expected expression: )
(9634,37-38): run-time error JS1004: Expected ';': {
 */
/*!
 * modernizr v3.6.0
 * Build https://modernizr.com/download?-inputtypes-touchevents-mq-setclasses-dontmin
 *
 * Copyright (c)
 *  Faruk Ates
 *  Paul Irish
 *  Alex Sexton
 *  Ryan Seddon
 *  Patrick Kettner
 *  Stu Cox
 *  Richard Herrera

 * MIT License
 */

/*
 * Modernizr tests which native CSS3 and HTML5 features are available in the
 * current UA and makes the results available to you in two ways: as properties on
 * a global `Modernizr` object, and as classes on the `<html>` element. This
 * information allows you to progressively enhance your pages with a granular level
 * of control over the experience.
*/

; (function (window, document, undefined) {
	var classes = [];


	var tests = [];


	/**
	 *
	 * ModernizrProto is the constructor for Modernizr
	 *
	 * @class
	 * @access public
	 */

	var ModernizrProto = {
		// The current version, dummy
		_version: '3.6.0',

		// Any settings that don't work as separate modules
		// can go in here as configuration.
		_config: {
			'classPrefix': '',
			'enableClasses': true,
			'enableJSClass': true,
			'usePrefixes': true
		},

		// Queue of tests
		_q: [],

		// Stub these for people who are listening
		on: function (test, cb) {
			// I don't really think people should do this, but we can
			// safe guard it a bit.
			// -- NOTE:: this gets WAY overridden in src/addTest for actual async tests.
			// This is in case people listen to synchronous tests. I would leave it out,
			// but the code to *disallow* sync tests in the real version of this
			// function is actually larger than this.
			var self = this;
			setTimeout(function () {
				cb(self[test]);
			}, 0);
		},

		addTest: function (name, fn, options) {
			tests.push({ name: name, fn: fn, options: options });
		},

		addAsyncTest: function (fn) {
			tests.push({ name: null, fn: fn });
		}
	};



	// Fake some of Object.create so we can force non test results to be non "own" properties.
	var Modernizr = function () { };
	Modernizr.prototype = ModernizrProto;

	// Leak modernizr globally when you `require` it rather than force it here.
	// Overwrite name so constructor name is nicer :D
	Modernizr = new Modernizr();



	/**
	 * is returns a boolean if the typeof an obj is exactly type.
	 *
	 * @access private
	 * @function is
	 * @param {*} obj - A thing we want to check the type of
	 * @param {string} type - A string to compare the typeof against
	 * @returns {boolean}
	 */

	function is(obj, type) {
		return typeof obj === type;
	}
	;

	/**
	 * Run through all tests and detect their support in the current UA.
	 *
	 * @access private
	 */

	function testRunner() {
		var featureNames;
		var feature;
		var aliasIdx;
		var result;
		var nameIdx;
		var featureName;
		var featureNameSplit;

		for (var featureIdx in tests) {
			if (tests.hasOwnProperty(featureIdx)) {
				featureNames = [];
				feature = tests[featureIdx];
				// run the test, throw the return value into the Modernizr,
				// then based on that boolean, define an appropriate className
				// and push it into an array of classes we'll join later.
				//
				// If there is no name, it's an 'async' test that is run,
				// but not directly added to the object. That should
				// be done with a post-run addTest call.
				if (feature.name) {
					featureNames.push(feature.name.toLowerCase());

					if (feature.options && feature.options.aliases && feature.options.aliases.length) {
						// Add all the aliases into the names list
						for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) {
							featureNames.push(feature.options.aliases[aliasIdx].toLowerCase());
						}
					}
				}

				// Run the test, or use the raw value if it's not a function
				result = is(feature.fn, 'function') ? feature.fn() : feature.fn;


				// Set each of the names on the Modernizr object
				for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) {
					featureName = featureNames[nameIdx];
					// Support dot properties as sub tests. We don't do checking to make sure
					// that the implied parent tests have been added. You must call them in
					// order (either in the test, or make the parent test a dependency).
					//
					// Cap it to TWO to make the logic simple and because who needs that kind of subtesting
					// hashtag famous last words
					featureNameSplit = featureName.split('.');

					if (featureNameSplit.length === 1) {
						Modernizr[featureNameSplit[0]] = result;
					} else {
						// cast to a Boolean, if not one already
						if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) {
							Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]);
						}

						Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result;
					}

					classes.push((result ? '' : 'no-') + featureNameSplit.join('-'));
				}
			}
		}
	}
	;

	/**
	 * docElement is a convenience wrapper to grab the root element of the document
	 *
	 * @access private
	 * @returns {HTMLElement|SVGElement} The root element of the document
	 */

	var docElement = document.documentElement;


	/**
	 * A convenience helper to check if the document we are running in is an SVG document
	 *
	 * @access private
	 * @returns {boolean}
	 */

	var isSVG = docElement.nodeName.toLowerCase() === 'svg';


	/**
	 * setClasses takes an array of class names and adds them to the root element
	 *
	 * @access private
	 * @function setClasses
	 * @param {string[]} classes - Array of class names
	 */

	// Pass in an and array of class names, e.g.:
	//  ['no-webp', 'borderradius', ...]
	function setClasses(classes) {
		var className = docElement.className;
		var classPrefix = Modernizr._config.classPrefix || '';

		if (isSVG) {
			className = className.baseVal;
		}

		// Change `no-js` to `js` (independently of the `enableClasses` option)
		// Handle classPrefix on this too
		if (Modernizr._config.enableJSClass) {
			var reJS = new RegExp('(^|\\s)' + classPrefix + 'no-js(\\s|$)');
			className = className.replace(reJS, '$1' + classPrefix + 'js$2');
		}

		if (Modernizr._config.enableClasses) {
			// Add the new classes
			className += ' ' + classPrefix + classes.join(' ' + classPrefix);
			if (isSVG) {
				docElement.className.baseVal = className;
			} else {
				docElement.className = className;
			}
		}

	}

	;

	/**
	 * createElement is a convenience wrapper around document.createElement. Since we
	 * use createElement all over the place, this allows for (slightly) smaller code
	 * as well as abstracting away issues with creating elements in contexts other than
	 * HTML documents (e.g. SVG documents).
	 *
	 * @access private
	 * @function createElement
	 * @returns {HTMLElement|SVGElement} An HTML or SVG element
	 */

	function createElement() {
		if (typeof document.createElement !== 'function') {
			// This is the case in IE7, where the type of createElement is "object".
			// For this reason, we cannot call apply() as Object is not a Function.
			return document.createElement(arguments[0]);
		} else if (isSVG) {
			return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]);
		} else {
			return document.createElement.apply(document, arguments);
		}
	}

	;

	/**
	 * since we have a fairly large number of input tests that don't mutate the input
	 * we create a single element that can be shared with all of those tests for a
	 * minor perf boost
	 *
	 * @access private
	 * @returns {HTMLInputElement}
	 */
	var inputElem = createElement('input');

	/*!
	{
	  "name": "Form input types",
	  "property": "inputtypes",
	  "caniuse": "forms",
	  "tags": ["forms"],
	  "authors": ["Mike Taylor"],
	  "polyfills": [
		"jquerytools",
		"webshims",
		"h5f",
		"webforms2",
		"nwxforms",
		"fdslider",
		"html5slider",
		"galleryhtml5forms",
		"jscolor",
		"html5formshim",
		"selectedoptionsjs",
		"formvalidationjs"
	  ]
	}
	!*/
	/* DOC
	Detects support for HTML5 form input types and exposes Boolean subproperties with the results:
	
	```javascript
	Modernizr.inputtypes.color
	Modernizr.inputtypes.date
	Modernizr.inputtypes.datetime
	Modernizr.inputtypes['datetime-local']
	Modernizr.inputtypes.email
	Modernizr.inputtypes.month
	Modernizr.inputtypes.number
	Modernizr.inputtypes.range
	Modernizr.inputtypes.search
	Modernizr.inputtypes.tel
	Modernizr.inputtypes.time
	Modernizr.inputtypes.url
	Modernizr.inputtypes.week
	```
	*/

	// Run through HTML5's new input types to see if the UA understands any.
	//   This is put behind the tests runloop because it doesn't return a
	//   true/false like all the other tests; instead, it returns an object
	//   containing each input type with its corresponding true/false value

	// Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/
	var inputtypes = 'search tel url email datetime date month week time datetime-local number range color'.split(' ');
	var inputs = {};

	Modernizr.inputtypes = (function (props) {
		var len = props.length;
		var smile = '1)';
		var inputElemType;
		var defaultView;
		var bool;

		for (var i = 0; i < len; i++) {

			inputElem.setAttribute('type', inputElemType = props[i]);
			bool = inputElem.type !== 'text' && 'style' in inputElem;

			// We first check to see if the type we give it sticks..
			// If the type does, we feed it a textual value, which shouldn't be valid.
			// If the value doesn't stick, we know there's input sanitization which infers a custom UI
			if (bool) {

				inputElem.value = smile;
				inputElem.style.cssText = 'position:absolute;visibility:hidden;';

				if (/^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined) {

					docElement.appendChild(inputElem);
					defaultView = document.defaultView;

					// Safari 2-4 allows the smiley as a value, despite making a slider
					bool = defaultView.getComputedStyle &&
						defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
						// Mobile android web browser has false positive, so must
						// check the height to see if the widget is actually there.
						(inputElem.offsetHeight !== 0);

					docElement.removeChild(inputElem);

				} else if (/^(search|tel)$/.test(inputElemType)) {
					// Spec doesn't define any special parsing or detectable UI
					//   behaviors so we pass these through as true

					// Interestingly, opera fails the earlier test, so it doesn't
					//  even make it here.

				} else if (/^(url|email)$/.test(inputElemType)) {
					// Real url and email support comes with prebaked validation.
					bool = inputElem.checkValidity && inputElem.checkValidity() === false;

				} else {
					// If the upgraded input compontent rejects the :) text, we got a winner
					bool = inputElem.value != smile;
				}
			}

			inputs[props[i]] = !!bool;
		}
		return inputs;
	})(inputtypes);


	/**
	 * getBody returns the body of a document, or an element that can stand in for
	 * the body if a real body does not exist
	 *
	 * @access private
	 * @function getBody
	 * @returns {HTMLElement|SVGElement} Returns the real body of a document, or an
	 * artificially created element that stands in for the body
	 */

	function getBody() {
		// After page load injecting a fake body doesn't work so check if body exists
		var body = document.body;

		if (!body) {
			// Can't use the real body create a fake one.
			body = createElement(isSVG ? 'svg' : 'body');
			body.fake = true;
		}

		return body;
	}

	;

	/**
	 * injectElementWithStyles injects an element with style element and some CSS rules
	 *
	 * @access private
	 * @function injectElementWithStyles
	 * @param {string} rule - String representing a css rule
	 * @param {function} callback - A function that is used to test the injected element
	 * @param {number} [nodes] - An integer representing the number of additional nodes you want injected
	 * @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes
	 * @returns {boolean}
	 */

	function injectElementWithStyles(rule, callback, nodes, testnames) {
		var mod = 'modernizr';
		var style;
		var ret;
		var node;
		var docOverflow;
		var div = createElement('div');
		var body = getBody();

		if (parseInt(nodes, 10)) {
			// In order not to give false positives we create a node for each test
			// This also allows the method to scale for unspecified uses
			while (nodes--) {
				node = createElement('div');
				node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
				div.appendChild(node);
			}
		}

		style = createElement('style');
		style.type = 'text/css';
		style.id = 's' + mod;

		// IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
		// Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
		(!body.fake ? div : body).appendChild(style);
		body.appendChild(div);

		if (style.styleSheet) {
			style.styleSheet.cssText = rule;
		} else {
			style.appendChild(document.createTextNode(rule));
		}
		div.id = mod;

		if (body.fake) {
			//avoid crashing IE8, if background image is used
			body.style.background = '';
			//Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
			body.style.overflow = 'hidden';
			docOverflow = docElement.style.overflow;
			docElement.style.overflow = 'hidden';
			docElement.appendChild(body);
		}

		ret = callback(div, rule);
		// If this is done after page load we don't want to remove the body so check if body exists
		if (body.fake) {
			body.parentNode.removeChild(body);
			docElement.style.overflow = docOverflow;
			// Trigger layout so kinetic scrolling isn't disabled in iOS6+
			// eslint-disable-next-line
			docElement.offsetHeight;
		} else {
			div.parentNode.removeChild(div);
		}

		return !!ret;

	}

	;

	/**
	 * Modernizr.mq tests a given media query, live against the current state of the window
	 * adapted from matchMedia polyfill by Scott Jehl and Paul Irish
	 * gist.github.com/786768
	 *
	 * @memberof Modernizr
	 * @name Modernizr.mq
	 * @optionName Modernizr.mq()
	 * @optionProp mq
	 * @access public
	 * @function mq
	 * @param {string} mq - String of the media query we want to test
	 * @returns {boolean}
	 * @example
	 * Modernizr.mq allows for you to programmatically check if the current browser
	 * window state matches a media query.
	 *
	 * ```js
	 *  var query = Modernizr.mq('(min-width: 900px)');
	 *
	 *  if (query) {
	 *    // the browser window is larger than 900px
	 *  }
	 * ```
	 *
	 * Only valid media queries are supported, therefore you must always include values
	 * with your media query
	 *
	 * ```js
	 * // good
	 *  Modernizr.mq('(min-width: 900px)');
	 *
	 * // bad
	 *  Modernizr.mq('min-width');
	 * ```
	 *
	 * If you would just like to test that media queries are supported in general, use
	 *
	 * ```js
	 *  Modernizr.mq('only all'); // true if MQ are supported, false if not
	 * ```
	 *
	 *
	 * Note that if the browser does not support media queries (e.g. old IE) mq will
	 * always return false.
	 */

	var mq = (function () {
		var matchMedia = window.matchMedia || window.msMatchMedia;
		if (matchMedia) {
			return function (mq) {
				var mql = matchMedia(mq);
				return mql && mql.matches || false;
			};
		}

		return function (mq) {
			var bool = false;

			injectElementWithStyles('@media ' + mq + ' { #modernizr { position: absolute; } }', function (node) {
				bool = (window.getComputedStyle ?
					window.getComputedStyle(node, null) :
					node.currentStyle).position == 'absolute';
			});

			return bool;
		};
	})();


	ModernizrProto.mq = mq;



	/**
	 * List of property values to set for css tests. See ticket #21
	 * http://git.io/vUGl4
	 *
	 * @memberof Modernizr
	 * @name Modernizr._prefixes
	 * @optionName Modernizr._prefixes
	 * @optionProp prefixes
	 * @access public
	 * @example
	 *
	 * Modernizr._prefixes is the internal list of prefixes that we test against
	 * inside of things like [prefixed](#modernizr-prefixed) and [prefixedCSS](#-code-modernizr-prefixedcss). It is simply
	 * an array of kebab-case vendor prefixes you can use within your code.
	 *
	 * Some common use cases include
	 *
	 * Generating all possible prefixed version of a CSS property
	 * ```js
	 * var rule = Modernizr._prefixes.join('transform: rotate(20deg); ');
	 *
	 * rule === 'transform: rotate(20deg); webkit-transform: rotate(20deg); moz-transform: rotate(20deg); o-transform: rotate(20deg); ms-transform: rotate(20deg);'
	 * ```
	 *
	 * Generating all possible prefixed version of a CSS value
	 * ```js
	 * rule = 'display:' +  Modernizr._prefixes.join('flex; display:') + 'flex';
	 *
	 * rule === 'display:flex; display:-webkit-flex; display:-moz-flex; display:-o-flex; display:-ms-flex; display:flex'
	 * ```
	 */

	// we use ['',''] rather than an empty array in order to allow a pattern of .`join()`ing prefixes to test
	// values in feature detects to continue to work
	var prefixes = (ModernizrProto._config.usePrefixes ? ' -webkit- -moz- -o- -ms- '.split(' ') : ['', '']);

	// expose these for the plugin API. Look in the source for how to join() them against your input
	ModernizrProto._prefixes = prefixes;



	/**
	 * testStyles injects an element with style element and some CSS rules
	 *
	 * @memberof Modernizr
	 * @name Modernizr.testStyles
	 * @optionName Modernizr.testStyles()
	 * @optionProp testStyles
	 * @access public
	 * @function testStyles
	 * @param {string} rule - String representing a css rule
	 * @param {function} callback - A function that is used to test the injected element
	 * @param {number} [nodes] - An integer representing the number of additional nodes you want injected
	 * @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes
	 * @returns {boolean}
	 * @example
	 *
	 * `Modernizr.testStyles` takes a CSS rule and injects it onto the current page
	 * along with (possibly multiple) DOM elements. This lets you check for features
	 * that can not be detected by simply checking the [IDL](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Interface_development_guide/IDL_interface_rules).
	 *
	 * ```js
	 * Modernizr.testStyles('#modernizr { width: 9px; color: papayawhip; }', function(elem, rule) {
	 *   // elem is the first DOM node in the page (by default #modernizr)
	 *   // rule is the first argument you supplied - the CSS rule in string form
	 *
	 *   addTest('widthworks', elem.style.width === '9px')
	 * });
	 * ```
	 *
	 * If your test requires multiple nodes, you can include a third argument
	 * indicating how many additional div elements to include on the page. The
	 * additional nodes are injected as children of the `elem` that is returned as
	 * the first argument to the callback.
	 *
	 * ```js
	 * Modernizr.testStyles('#modernizr {width: 1px}; #modernizr2 {width: 2px}', function(elem) {
	 *   document.getElementById('modernizr').style.width === '1px'; // true
	 *   document.getElementById('modernizr2').style.width === '2px'; // true
	 *   elem.firstChild === document.getElementById('modernizr2'); // true
	 * }, 1);
	 * ```
	 *
	 * By default, all of the additional elements have an ID of `modernizr[n]`, where
	 * `n` is its index (e.g. the first additional, second overall is `#modernizr2`,
	 * the second additional is `#modernizr3`, etc.).
	 * If you want to have more meaningful IDs for your function, you can provide
	 * them as the fourth argument, as an array of strings
	 *
	 * ```js
	 * Modernizr.testStyles('#foo {width: 10px}; #bar {height: 20px}', function(elem) {
	 *   elem.firstChild === document.getElementById('foo'); // true
	 *   elem.lastChild === document.getElementById('bar'); // true
	 * }, 2, ['foo', 'bar']);
	 * ```
	 *
	 */

	var testStyles = ModernizrProto.testStyles = injectElementWithStyles;

	/*!
	{
	  "name": "Touch Events",
	  "property": "touchevents",
	  "caniuse" : "touch",
	  "tags": ["media", "attribute"],
	  "notes": [{
		"name": "Touch Events spec",
		"href": "https://www.w3.org/TR/2013/WD-touch-events-20130124/"
	  }],
	  "warnings": [
		"Indicates if the browser supports the Touch Events spec, and does not necessarily reflect a touchscreen device"
	  ],
	  "knownBugs": [
		"False-positive on some configurations of Nokia N900",
		"False-positive on some BlackBerry 6.0 builds – https://github.com/Modernizr/Modernizr/issues/372#issuecomment-3112695"
	  ]
	}
	!*/
	/* DOC
	Indicates if the browser supports the W3C Touch Events API.
	
	This *does not* necessarily reflect a touchscreen device:
	
	* Older touchscreen devices only emulate mouse events
	* Modern IE touch devices implement the Pointer Events API instead: use `Modernizr.pointerevents` to detect support for that
	* Some browsers & OS setups may enable touch APIs when no touchscreen is connected
	* Future browsers may implement other event models for touch interactions
	
	See this article: [You Can't Detect A Touchscreen](http://www.stucox.com/blog/you-cant-detect-a-touchscreen/).
	
	It's recommended to bind both mouse and touch/pointer events simultaneously – see [this HTML5 Rocks tutorial](http://www.html5rocks.com/en/mobile/touchandmouse/).
	
	This test will also return `true` for Firefox 4 Multitouch support.
	*/

	// Chrome (desktop) used to lie about its support on this, but that has since been rectified: http://crbug.com/36415
	Modernizr.addTest('touchevents', function () {
		var bool;
		if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
			bool = true;
		} else {
			// include the 'heartz' as a way to have a non matching MQ to help terminate the join
			// https://git.io/vznFH
			var query = ['@media (', prefixes.join('touch-enabled),('), 'heartz', ')', '{#modernizr{top:9px;position:absolute}}'].join('');
			testStyles(query, function (node) {
				bool = node.offsetTop === 9;
			});
		}
		return bool;
	});


	// Run each test
	testRunner();

	// Remove the "no-js" class if it exists
	setClasses(classes);

	delete ModernizrProto.addTest;
	delete ModernizrProto.addAsyncTest;

	// Run the things that are supposed to run after the tests
	for (var i = 0; i < Modernizr._q.length; i++) {
		Modernizr._q[i]();
	}

	// Leak Modernizr namespace
	window.Modernizr = Modernizr;


	;

})(window, document);;
/*!
 * Flickity PACKAGED v2.2.1
 * Touch, responsive, flickable carousels
 *
 * Licensed GPLv3 for open source use
 * or Flickity Commercial License for commercial use
 *
 * https://flickity.metafizzy.co
 * Copyright 2015-2019 Metafizzy
 */

/**
 * Bridget makes jQuery widgets
 * v2.0.1
 * MIT license
 */

/* jshint browser: true, strict: true, undef: true, unused: true */

( function( window, factory ) {
    // universal module definition
    /*jshint strict: false */ /* globals define, module, require */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'jquery-bridget/jquery-bridget',[ 'jquery' ], function( jQuery ) {
        return factory( window, jQuery );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('jquery')
      );
    } else {
      // browser global
      window.jQueryBridget = factory(
        window,
        window.jQuery
      );
    }
  
  }( window, function factory( window, jQuery ) {
  'use strict';
  
  // ----- utils ----- //
  
  var arraySlice = Array.prototype.slice;
  
  // helper function for logging errors
  // $.error breaks jQuery chaining
  var console = window.console;
  var logError = typeof console == 'undefined' ? function() {} :
    function( message ) {
      console.error( message );
    };
  
  // ----- jQueryBridget ----- //
  
  function jQueryBridget( namespace, PluginClass, $ ) {
    $ = $ || jQuery || window.jQuery;
    if ( !$ ) {
      return;
    }
  
    // add option method -> $().plugin('option', {...})
    if ( !PluginClass.prototype.option ) {
      // option setter
      PluginClass.prototype.option = function( opts ) {
        // bail out if not an object
        if ( !$.isPlainObject( opts ) ){
          return;
        }
        this.options = $.extend( true, this.options, opts );
      };
    }
  
    // make jQuery plugin
    $.fn[ namespace ] = function( arg0 /*, arg1 */ ) {
      if ( typeof arg0 == 'string' ) {
        // method call $().plugin( 'methodName', { options } )
        // shift arguments by 1
        var args = arraySlice.call( arguments, 1 );
        return methodCall( this, arg0, args );
      }
      // just $().plugin({ options })
      plainCall( this, arg0 );
      return this;
    };
  
    // $().plugin('methodName')
    function methodCall( $elems, methodName, args ) {
      var returnValue;
      var pluginMethodStr = '$().' + namespace + '("' + methodName + '")';
  
      $elems.each( function( i, elem ) {
        // get instance
        var instance = $.data( elem, namespace );
        if ( !instance ) {
          logError( namespace + ' not initialized. Cannot call methods, i.e. ' +
            pluginMethodStr );
          return;
        }
  
        var method = instance[ methodName ];
        if ( !method || methodName.charAt(0) == '_' ) {
          logError( pluginMethodStr + ' is not a valid method' );
          return;
        }
  
        // apply method, get return value
        var value = method.apply( instance, args );
        // set return value if value is returned, use only first value
        returnValue = returnValue === undefined ? value : returnValue;
      });
  
      return returnValue !== undefined ? returnValue : $elems;
    }
  
    function plainCall( $elems, options ) {
      $elems.each( function( i, elem ) {
        var instance = $.data( elem, namespace );
        if ( instance ) {
          // set options & init
          instance.option( options );
          instance._init();
        } else {
          // initialize new instance
          instance = new PluginClass( elem, options );
          $.data( elem, namespace, instance );
        }
      });
    }
  
    updateJQuery( $ );
  
  }
  
  // ----- updateJQuery ----- //
  
  // set $.bridget for v1 backwards compatibility
  function updateJQuery( $ ) {
    if ( !$ || ( $ && $.bridget ) ) {
      return;
    }
    $.bridget = jQueryBridget;
  }
  
  updateJQuery( jQuery || window.jQuery );
  
  // -----  ----- //
  
  return jQueryBridget;
  
  }));
  
  /**
   * EvEmitter v1.1.0
   * Lil' event emitter
   * MIT License
   */
  
  /* jshint unused: true, undef: true, strict: true */
  
  ( function( global, factory ) {
    // universal module definition
    /* jshint strict: false */ /* globals define, module, window */
    if ( typeof define == 'function' && define.amd ) {
      // AMD - RequireJS
      define( 'ev-emitter/ev-emitter',factory );
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS - Browserify, Webpack
      module.exports = factory();
    } else {
      // Browser globals
      global.EvEmitter = factory();
    }
  
  }( typeof window != 'undefined' ? window : this, function() {
  
  
  
  function EvEmitter() {}
  
  var proto = EvEmitter.prototype;
  
  proto.on = function( eventName, listener ) {
    if ( !eventName || !listener ) {
      return;
    }
    // set events hash
    var events = this._events = this._events || {};
    // set listeners array
    var listeners = events[ eventName ] = events[ eventName ] || [];
    // only add once
    if ( listeners.indexOf( listener ) == -1 ) {
      listeners.push( listener );
    }
  
    return this;
  };
  
  proto.once = function( eventName, listener ) {
    if ( !eventName || !listener ) {
      return;
    }
    // add event
    this.on( eventName, listener );
    // set once flag
    // set onceEvents hash
    var onceEvents = this._onceEvents = this._onceEvents || {};
    // set onceListeners object
    var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
    // set flag
    onceListeners[ listener ] = true;
  
    return this;
  };
  
  proto.off = function( eventName, listener ) {
    var listeners = this._events && this._events[ eventName ];
    if ( !listeners || !listeners.length ) {
      return;
    }
    var index = listeners.indexOf( listener );
    if ( index != -1 ) {
      listeners.splice( index, 1 );
    }
  
    return this;
  };
  
  proto.emitEvent = function( eventName, args ) {
    var listeners = this._events && this._events[ eventName ];
    if ( !listeners || !listeners.length ) {
      return;
    }
    // copy over to avoid interference if .off() in listener
    listeners = listeners.slice(0);
    args = args || [];
    // once stuff
    var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
  
    for ( var i=0; i < listeners.length; i++ ) {
      var listener = listeners[i]
      var isOnce = onceListeners && onceListeners[ listener ];
      if ( isOnce ) {
        // remove listener
        // remove before trigger to prevent recursion
        this.off( eventName, listener );
        // unset once flag
        delete onceListeners[ listener ];
      }
      // trigger listener
      listener.apply( this, args );
    }
  
    return this;
  };
  
  proto.allOff = function() {
    delete this._events;
    delete this._onceEvents;
  };
  
  return EvEmitter;
  
  }));
  
  /*!
   * getSize v2.0.3
   * measure size of elements
   * MIT license
   */
  
  /* jshint browser: true, strict: true, undef: true, unused: true */
  /* globals console: false */
  
  ( function( window, factory ) {
    /* jshint strict: false */ /* globals define, module */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'get-size/get-size',factory );
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory();
    } else {
      // browser global
      window.getSize = factory();
    }
  
  })( window, function factory() {
  'use strict';
  
  // -------------------------- helpers -------------------------- //
  
  // get a number from a string, not a percentage
  function getStyleSize( value ) {
    var num = parseFloat( value );
    // not a percent like '100%', and a number
    var isValid = value.indexOf('%') == -1 && !isNaN( num );
    return isValid && num;
  }
  
  function noop() {}
  
  var logError = typeof console == 'undefined' ? noop :
    function( message ) {
      console.error( message );
    };
  
  // -------------------------- measurements -------------------------- //
  
  var measurements = [
    'paddingLeft',
    'paddingRight',
    'paddingTop',
    'paddingBottom',
    'marginLeft',
    'marginRight',
    'marginTop',
    'marginBottom',
    'borderLeftWidth',
    'borderRightWidth',
    'borderTopWidth',
    'borderBottomWidth'
  ];
  
  var measurementsLength = measurements.length;
  
  function getZeroSize() {
    var size = {
      width: 0,
      height: 0,
      innerWidth: 0,
      innerHeight: 0,
      outerWidth: 0,
      outerHeight: 0
    };
    for ( var i=0; i < measurementsLength; i++ ) {
      var measurement = measurements[i];
      size[ measurement ] = 0;
    }
    return size;
  }
  
  // -------------------------- getStyle -------------------------- //
  
  /**
   * getStyle, get style of element, check for Firefox bug
   * https://bugzilla.mozilla.org/show_bug.cgi?id=548397
   */
  function getStyle( elem ) {
    var style = getComputedStyle( elem );
    if ( !style ) {
      logError( 'Style returned ' + style +
        '. Are you running this code in a hidden iframe on Firefox? ' +
        'See https://bit.ly/getsizebug1' );
    }
    return style;
  }
  
  // -------------------------- setup -------------------------- //
  
  var isSetup = false;
  
  var isBoxSizeOuter;
  
  /**
   * setup
   * check isBoxSizerOuter
   * do on first getSize() rather than on page load for Firefox bug
   */
  function setup() {
    // setup once
    if ( isSetup ) {
      return;
    }
    isSetup = true;
  
    // -------------------------- box sizing -------------------------- //
  
    /**
     * Chrome & Safari measure the outer-width on style.width on border-box elems
     * IE11 & Firefox<29 measures the inner-width
     */
    var div = document.createElement('div');
    div.style.width = '200px';
    div.style.padding = '1px 2px 3px 4px';
    div.style.borderStyle = 'solid';
    div.style.borderWidth = '1px 2px 3px 4px';
    div.style.boxSizing = 'border-box';
  
    var body = document.body || document.documentElement;
    body.appendChild( div );
    var style = getStyle( div );
    // round value for browser zoom. desandro/masonry#928
    isBoxSizeOuter = Math.round( getStyleSize( style.width ) ) == 200;
    getSize.isBoxSizeOuter = isBoxSizeOuter;
  
    body.removeChild( div );
  }
  
  // -------------------------- getSize -------------------------- //
  
  function getSize( elem ) {
    setup();
  
    // use querySeletor if elem is string
    if ( typeof elem == 'string' ) {
      elem = document.querySelector( elem );
    }
  
    // do not proceed on non-objects
    if ( !elem || typeof elem != 'object' || !elem.nodeType ) {
      return;
    }
  
    var style = getStyle( elem );
  
    // if hidden, everything is 0
    if ( style.display == 'none' ) {
      return getZeroSize();
    }
  
    var size = {};
    size.width = elem.offsetWidth;
    size.height = elem.offsetHeight;
  
    var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box';
  
    // get all measurements
    for ( var i=0; i < measurementsLength; i++ ) {
      var measurement = measurements[i];
      var value = style[ measurement ];
      var num = parseFloat( value );
      // any 'auto', 'medium' value will be 0
      size[ measurement ] = !isNaN( num ) ? num : 0;
    }
  
    var paddingWidth = size.paddingLeft + size.paddingRight;
    var paddingHeight = size.paddingTop + size.paddingBottom;
    var marginWidth = size.marginLeft + size.marginRight;
    var marginHeight = size.marginTop + size.marginBottom;
    var borderWidth = size.borderLeftWidth + size.borderRightWidth;
    var borderHeight = size.borderTopWidth + size.borderBottomWidth;
  
    var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;
  
    // overwrite width and height if we can get it from style
    var styleWidth = getStyleSize( style.width );
    if ( styleWidth !== false ) {
      size.width = styleWidth +
        // add padding and border unless it's already including it
        ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth );
    }
  
    var styleHeight = getStyleSize( style.height );
    if ( styleHeight !== false ) {
      size.height = styleHeight +
        // add padding and border unless it's already including it
        ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight );
    }
  
    size.innerWidth = size.width - ( paddingWidth + borderWidth );
    size.innerHeight = size.height - ( paddingHeight + borderHeight );
  
    size.outerWidth = size.width + marginWidth;
    size.outerHeight = size.height + marginHeight;
  
    return size;
  }
  
  return getSize;
  
  });
  
  /**
   * matchesSelector v2.0.2
   * matchesSelector( element, '.selector' )
   * MIT license
   */
  
  /*jshint browser: true, strict: true, undef: true, unused: true */
  
  ( function( window, factory ) {
    /*global define: false, module: false */
    'use strict';
    // universal module definition
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'desandro-matches-selector/matches-selector',factory );
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory();
    } else {
      // browser global
      window.matchesSelector = factory();
    }
  
  }( window, function factory() {
    'use strict';
  
    var matchesMethod = ( function() {
      var ElemProto = window.Element.prototype;
      // check for the standard method name first
      if ( ElemProto.matches ) {
        return 'matches';
      }
      // check un-prefixed
      if ( ElemProto.matchesSelector ) {
        return 'matchesSelector';
      }
      // check vendor prefixes
      var prefixes = [ 'webkit', 'moz', 'ms', 'o' ];
  
      for ( var i=0; i < prefixes.length; i++ ) {
        var prefix = prefixes[i];
        var method = prefix + 'MatchesSelector';
        if ( ElemProto[ method ] ) {
          return method;
        }
      }
    })();
  
    return function matchesSelector( elem, selector ) {
      return elem[ matchesMethod ]( selector );
    };
  
  }));
  
  /**
   * Fizzy UI utils v2.0.7
   * MIT license
   */
  
  /*jshint browser: true, undef: true, unused: true, strict: true */
  
  ( function( window, factory ) {
    // universal module definition
    /*jshint strict: false */ /*globals define, module, require */
  
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'fizzy-ui-utils/utils',[
        'desandro-matches-selector/matches-selector'
      ], function( matchesSelector ) {
        return factory( window, matchesSelector );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('desandro-matches-selector')
      );
    } else {
      // browser global
      window.fizzyUIUtils = factory(
        window,
        window.matchesSelector
      );
    }
  
  }( window, function factory( window, matchesSelector ) {
  
  
  
  var utils = {};
  
  // ----- extend ----- //
  
  // extends objects
  utils.extend = function( a, b ) {
    for ( var prop in b ) {
      a[ prop ] = b[ prop ];
    }
    return a;
  };
  
  // ----- modulo ----- //
  
  utils.modulo = function( num, div ) {
    return ( ( num % div ) + div ) % div;
  };
  
  // ----- makeArray ----- //
  
  var arraySlice = Array.prototype.slice;
  
  // turn element or nodeList into an array
  utils.makeArray = function( obj ) {
    if ( Array.isArray( obj ) ) {
      // use object if already an array
      return obj;
    }
    // return empty array if undefined or null. #6
    if ( obj === null || obj === undefined ) {
      return [];
    }
  
    var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
    if ( isArrayLike ) {
      // convert nodeList to array
      return arraySlice.call( obj );
    }
  
    // array of single index
    return [ obj ];
  };
  
  // ----- removeFrom ----- //
  
  utils.removeFrom = function( ary, obj ) {
    var index = ary.indexOf( obj );
    if ( index != -1 ) {
      ary.splice( index, 1 );
    }
  };
  
  // ----- getParent ----- //
  
  utils.getParent = function( elem, selector ) {
    while ( elem.parentNode && elem != document.body ) {
      elem = elem.parentNode;
      if ( matchesSelector( elem, selector ) ) {
        return elem;
      }
    }
  };
  
  // ----- getQueryElement ----- //
  
  // use element as selector string
  utils.getQueryElement = function( elem ) {
    if ( typeof elem == 'string' ) {
      return document.querySelector( elem );
    }
    return elem;
  };
  
  // ----- handleEvent ----- //
  
  // enable .ontype to trigger from .addEventListener( elem, 'type' )
  utils.handleEvent = function( event ) {
    var method = 'on' + event.type;
    if ( this[ method ] ) {
      this[ method ]( event );
    }
  };
  
  // ----- filterFindElements ----- //
  
  utils.filterFindElements = function( elems, selector ) {
    // make array of elems
    elems = utils.makeArray( elems );
    var ffElems = [];
  
    elems.forEach( function( elem ) {
      // check that elem is an actual element
      if ( !( elem instanceof HTMLElement ) ) {
        return;
      }
      // add elem if no selector
      if ( !selector ) {
        ffElems.push( elem );
        return;
      }
      // filter & find items if we have a selector
      // filter
      if ( matchesSelector( elem, selector ) ) {
        ffElems.push( elem );
      }
      // find children
      var childElems = elem.querySelectorAll( selector );
      // concat childElems to filterFound array
      for ( var i=0; i < childElems.length; i++ ) {
        ffElems.push( childElems[i] );
      }
    });
  
    return ffElems;
  };
  
  // ----- debounceMethod ----- //
  
  utils.debounceMethod = function( _class, methodName, threshold ) {
    threshold = threshold || 100;
    // original method
    var method = _class.prototype[ methodName ];
    var timeoutName = methodName + 'Timeout';
  
    _class.prototype[ methodName ] = function() {
      var timeout = this[ timeoutName ];
      clearTimeout( timeout );
  
      var args = arguments;
      var _this = this;
      this[ timeoutName ] = setTimeout( function() {
        method.apply( _this, args );
        delete _this[ timeoutName ];
      }, threshold );
    };
  };
  
  // ----- docReady ----- //
  
  utils.docReady = function( callback ) {
    var readyState = document.readyState;
    if ( readyState == 'complete' || readyState == 'interactive' ) {
      // do async to allow for other scripts to run. metafizzy/flickity#441
      setTimeout( callback );
    } else {
      document.addEventListener( 'DOMContentLoaded', callback );
    }
  };
  
  // ----- htmlInit ----- //
  
  // http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/
  utils.toDashed = function( str ) {
    return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
      return $1 + '-' + $2;
    }).toLowerCase();
  };
  
  var console = window.console;
  /**
   * allow user to initialize classes via [data-namespace] or .js-namespace class
   * htmlInit( Widget, 'widgetName' )
   * options are parsed from data-namespace-options
   */
  utils.htmlInit = function( WidgetClass, namespace ) {
    utils.docReady( function() {
      var dashedNamespace = utils.toDashed( namespace );
      var dataAttr = 'data-' + dashedNamespace;
      var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' );
      var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace );
      var elems = utils.makeArray( dataAttrElems )
        .concat( utils.makeArray( jsDashElems ) );
      var dataOptionsAttr = dataAttr + '-options';
      var jQuery = window.jQuery;
  
      elems.forEach( function( elem ) {
        var attr = elem.getAttribute( dataAttr ) ||
          elem.getAttribute( dataOptionsAttr );
        var options;
        try {
          options = attr && JSON.parse( attr );
        } catch ( error ) {
          // log error, do not initialize
          if ( console ) {
            console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className +
            ': ' + error );
          }
          return;
        }
        // initialize
        var instance = new WidgetClass( elem, options );
        // make available via $().data('namespace')
        if ( jQuery ) {
          jQuery.data( elem, namespace, instance );
        }
      });
  
    });
  };
  
  // -----  ----- //
  
  return utils;
  
  }));
  
  // Flickity.Cell
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/cell',[
        'get-size/get-size'
      ], function( getSize ) {
        return factory( window, getSize );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('get-size')
      );
    } else {
      // browser global
      window.Flickity = window.Flickity || {};
      window.Flickity.Cell = factory(
        window,
        window.getSize
      );
    }
  
  }( window, function factory( window, getSize ) {
  
  
  
  function Cell( elem, parent ) {
    this.element = elem;
    this.parent = parent;
  
    this.create();
  }
  
  var proto = Cell.prototype;
  
  proto.create = function() {
    this.element.style.position = 'absolute';
    this.element.setAttribute( 'aria-hidden', 'true' );
    this.x = 0;
    this.shift = 0;
  };
  
  proto.destroy = function() {
    // reset style
    this.unselect();
    this.element.style.position = '';
    var side = this.parent.originSide;
    this.element.style[ side ] = '';
  };
  
  proto.getSize = function() {
    this.size = getSize( this.element );
  };
  
  proto.setPosition = function( x ) {
    this.x = x;
    this.updateTarget();
    this.renderPosition( x );
  };
  
  // setDefaultTarget v1 method, backwards compatibility, remove in v3
  proto.updateTarget = proto.setDefaultTarget = function() {
    var marginProperty = this.parent.originSide == 'left' ? 'marginLeft' : 'marginRight';
    this.target = this.x + this.size[ marginProperty ] +
      this.size.width * this.parent.cellAlign;
  };
  
  proto.renderPosition = function( x ) {
    // render position of cell with in slider
    var side = this.parent.originSide;
    this.element.style[ side ] = this.parent.getPositionValue( x );
  };
  
  proto.select = function() {
    this.element.classList.add('is-selected');
    this.element.removeAttribute('aria-hidden');
  };
  
  proto.unselect = function() {
    this.element.classList.remove('is-selected');
    this.element.setAttribute( 'aria-hidden', 'true' );
  };
  
  /**
   * @param {Integer} factor - 0, 1, or -1
  **/
  proto.wrapShift = function( shift ) {
    this.shift = shift;
    this.renderPosition( this.x + this.parent.slideableWidth * shift );
  };
  
  proto.remove = function() {
    this.element.parentNode.removeChild( this.element );
  };
  
  return Cell;
  
  }));
  
  // slide
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/slide',factory );
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory();
    } else {
      // browser global
      window.Flickity = window.Flickity || {};
      window.Flickity.Slide = factory();
    }
  
  }( window, function factory() {
  'use strict';
  
  function Slide( parent ) {
    this.parent = parent;
    this.isOriginLeft = parent.originSide == 'left';
    this.cells = [];
    this.outerWidth = 0;
    this.height = 0;
  }
  
  var proto = Slide.prototype;
  
  proto.addCell = function( cell ) {
    this.cells.push( cell );
    this.outerWidth += cell.size.outerWidth;
    this.height = Math.max( cell.size.outerHeight, this.height );
    // first cell stuff
    if ( this.cells.length == 1 ) {
      this.x = cell.x; // x comes from first cell
      var beginMargin = this.isOriginLeft ? 'marginLeft' : 'marginRight';
      this.firstMargin = cell.size[ beginMargin ];
    }
  };
  
  proto.updateTarget = function() {
    var endMargin = this.isOriginLeft ? 'marginRight' : 'marginLeft';
    var lastCell = this.getLastCell();
    var lastMargin = lastCell ? lastCell.size[ endMargin ] : 0;
    var slideWidth = this.outerWidth - ( this.firstMargin + lastMargin );
    this.target = this.x + this.firstMargin + slideWidth * this.parent.cellAlign;
  };
  
  proto.getLastCell = function() {
    return this.cells[ this.cells.length - 1 ];
  };
  
  proto.select = function() {
    this.cells.forEach( function( cell ) {
      cell.select();
    });
  };
  
  proto.unselect = function() {
    this.cells.forEach( function( cell ) {
      cell.unselect();
    });
  };
  
  proto.getCellElements = function() {
    return this.cells.map( function( cell ) {
      return cell.element;
    });
  };
  
  return Slide;
  
  }));
  
  // animate
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/animate',[
        'fizzy-ui-utils/utils'
      ], function( utils ) {
        return factory( window, utils );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('fizzy-ui-utils')
      );
    } else {
      // browser global
      window.Flickity = window.Flickity || {};
      window.Flickity.animatePrototype = factory(
        window,
        window.fizzyUIUtils
      );
    }
  
  }( window, function factory( window, utils ) {
  
  
  
  // -------------------------- animate -------------------------- //
  
  var proto = {};
  
  proto.startAnimation = function() {
    if ( this.isAnimating ) {
      return;
    }
  
    this.isAnimating = true;
    this.restingFrames = 0;
    this.animate();
  };
  
  proto.animate = function() {
    this.applyDragForce();
    this.applySelectedAttraction();
  
    var previousX = this.x;
  
    this.integratePhysics();
    this.positionSlider();
    this.settle( previousX );
    // animate next frame
    if ( this.isAnimating ) {
      var _this = this;
      requestAnimationFrame( function animateFrame() {
        _this.animate();
      });
    }
  };
  
  proto.positionSlider = function() {
    var x = this.x;
    // wrap position around
    if ( this.options.wrapAround && this.cells.length > 1 ) {
      x = utils.modulo( x, this.slideableWidth );
      x = x - this.slideableWidth;
      this.shiftWrapCells( x );
    }
  
    this.setTranslateX( x, this.isAnimating );
    this.dispatchScrollEvent();
  };
  
  proto.setTranslateX = function( x, is3d ) {
    x += this.cursorPosition;
    // reverse if right-to-left and using transform
    x = this.options.rightToLeft ? -x : x;
    var translateX = this.getPositionValue( x );
    // use 3D tranforms for hardware acceleration on iOS
    // but use 2D when settled, for better font-rendering
    this.slider.style.transform = is3d ?
      'translate3d(' + translateX + ',0,0)' : 'translateX(' + translateX + ')';
  };
  
  proto.dispatchScrollEvent = function() {
    var firstSlide = this.slides[0];
    if ( !firstSlide ) {
      return;
    }
    var positionX = -this.x - firstSlide.target;
    var progress = positionX / this.slidesWidth;
    this.dispatchEvent( 'scroll', null, [ progress, positionX ] );
  };
  
  proto.positionSliderAtSelected = function() {
    if ( !this.cells.length ) {
      return;
    }
    this.x = -this.selectedSlide.target;
    this.velocity = 0; // stop wobble
    this.positionSlider();
  };
  
  proto.getPositionValue = function( position ) {
    if ( this.options.percentPosition ) {
      // percent position, round to 2 digits, like 12.34%
      return ( Math.round( ( position / this.size.innerWidth ) * 10000 ) * 0.01 )+ '%';
    } else {
      // pixel positioning
      return Math.round( position ) + 'px';
    }
  };
  
  proto.settle = function( previousX ) {
    // keep track of frames where x hasn't moved
    if ( !this.isPointerDown && Math.round( this.x * 100 ) == Math.round( previousX * 100 ) ) {
      this.restingFrames++;
    }
    // stop animating if resting for 3 or more frames
    if ( this.restingFrames > 2 ) {
      this.isAnimating = false;
      delete this.isFreeScrolling;
      // render position with translateX when settled
      this.positionSlider();
      this.dispatchEvent( 'settle', null, [ this.selectedIndex ] );
    }
  };
  
  proto.shiftWrapCells = function( x ) {
    // shift before cells
    var beforeGap = this.cursorPosition + x;
    this._shiftCells( this.beforeShiftCells, beforeGap, -1 );
    // shift after cells
    var afterGap = this.size.innerWidth - ( x + this.slideableWidth + this.cursorPosition );
    this._shiftCells( this.afterShiftCells, afterGap, 1 );
  };
  
  proto._shiftCells = function( cells, gap, shift ) {
    for ( var i=0; i < cells.length; i++ ) {
      var cell = cells[i];
      var cellShift = gap > 0 ? shift : 0;
      cell.wrapShift( cellShift );
      gap -= cell.size.outerWidth;
    }
  };
  
  proto._unshiftCells = function( cells ) {
    if ( !cells || !cells.length ) {
      return;
    }
    for ( var i=0; i < cells.length; i++ ) {
      cells[i].wrapShift( 0 );
    }
  };
  
  // -------------------------- physics -------------------------- //
  
  proto.integratePhysics = function() {
    this.x += this.velocity;
    this.velocity *= this.getFrictionFactor();
  };
  
  proto.applyForce = function( force ) {
    this.velocity += force;
  };
  
  proto.getFrictionFactor = function() {
    return 1 - this.options[ this.isFreeScrolling ? 'freeScrollFriction' : 'friction' ];
  };
  
  proto.getRestingPosition = function() {
    // my thanks to Steven Wittens, who simplified this math greatly
    return this.x + this.velocity / ( 1 - this.getFrictionFactor() );
  };
  
  proto.applyDragForce = function() {
    if ( !this.isDraggable || !this.isPointerDown ) {
      return;
    }
    // change the position to drag position by applying force
    var dragVelocity = this.dragX - this.x;
    var dragForce = dragVelocity - this.velocity;
    this.applyForce( dragForce );
  };
  
  proto.applySelectedAttraction = function() {
    // do not attract if pointer down or no slides
    var dragDown = this.isDraggable && this.isPointerDown;
    if ( dragDown || this.isFreeScrolling || !this.slides.length ) {
      return;
    }
    var distance = this.selectedSlide.target * -1 - this.x;
    var force = distance * this.options.selectedAttraction;
    this.applyForce( force );
  };
  
  return proto;
  
  }));
  
  // Flickity main
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/flickity',[
        'ev-emitter/ev-emitter',
        'get-size/get-size',
        'fizzy-ui-utils/utils',
        './cell',
        './slide',
        './animate'
      ], function( EvEmitter, getSize, utils, Cell, Slide, animatePrototype ) {
        return factory( window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('ev-emitter'),
        require('get-size'),
        require('fizzy-ui-utils'),
        require('./cell'),
        require('./slide'),
        require('./animate')
      );
    } else {
      // browser global
      var _Flickity = window.Flickity;
  
      window.Flickity = factory(
        window,
        window.EvEmitter,
        window.getSize,
        window.fizzyUIUtils,
        _Flickity.Cell,
        _Flickity.Slide,
        _Flickity.animatePrototype
      );
    }
  
  }( window, function factory( window, EvEmitter, getSize,
    utils, Cell, Slide, animatePrototype ) {
  
  
  
  // vars
  var jQuery = window.jQuery;
  var getComputedStyle = window.getComputedStyle;
  var console = window.console;
  
  function moveElements( elems, toElem ) {
    elems = utils.makeArray( elems );
    while ( elems.length ) {
      toElem.appendChild( elems.shift() );
    }
  }
  
  // -------------------------- Flickity -------------------------- //
  
  // globally unique identifiers
  var GUID = 0;
  // internal store of all Flickity intances
  var instances = {};
  
  function Flickity( element, options ) {
    var queryElement = utils.getQueryElement( element );
    if ( !queryElement ) {
      if ( console ) {
        console.error( 'Bad element for Flickity: ' + ( queryElement || element ) );
      }
      return;
    }
    this.element = queryElement;
    // do not initialize twice on same element
    if ( this.element.flickityGUID ) {
      var instance = instances[ this.element.flickityGUID ];
      instance.option( options );
      return instance;
    }
  
    // add jQuery
    if ( jQuery ) {
      this.$element = jQuery( this.element );
    }
    // options
    this.options = utils.extend( {}, this.constructor.defaults );
    this.option( options );
  
    // kick things off
    this._create();
  }
  
  Flickity.defaults = {
    accessibility: true,
    // adaptiveHeight: false,
    cellAlign: 'center',
    // cellSelector: undefined,
    // contain: false,
    freeScrollFriction: 0.075, // friction when free-scrolling
    friction: 0.28, // friction when selecting
    namespaceJQueryEvents: true,
    // initialIndex: 0,
    percentPosition: true,
    resize: true,
    selectedAttraction: 0.025,
    setGallerySize: true
    // watchCSS: false,
    // wrapAround: false
  };
  
  // hash of methods triggered on _create()
  Flickity.createMethods = [];
  
  var proto = Flickity.prototype;
  // inherit EventEmitter
  utils.extend( proto, EvEmitter.prototype );
  
  proto._create = function() {
    // add id for Flickity.data
    var id = this.guid = ++GUID;
    this.element.flickityGUID = id; // expando
    instances[ id ] = this; // associate via id
    // initial properties
    this.selectedIndex = 0;
    // how many frames slider has been in same position
    this.restingFrames = 0;
    // initial physics properties
    this.x = 0;
    this.velocity = 0;
    this.originSide = this.options.rightToLeft ? 'right' : 'left';
    // create viewport & slider
    this.viewport = document.createElement('div');
    this.viewport.className = 'flickity-viewport';
    this._createSlider();
  
    if ( this.options.resize || this.options.watchCSS ) {
      window.addEventListener( 'resize', this );
    }
  
    // add listeners from on option
    for ( var eventName in this.options.on ) {
      var listener = this.options.on[ eventName ];
      this.on( eventName, listener );
    }
  
    Flickity.createMethods.forEach( function( method ) {
      this[ method ]();
    }, this );
  
    if ( this.options.watchCSS ) {
      this.watchCSS();
    } else {
      this.activate();
    }
  
  };
  
  /**
   * set options
   * @param {Object} opts
   */
  proto.option = function( opts ) {
    utils.extend( this.options, opts );
  };
  
  proto.activate = function() {
    if ( this.isActive ) {
      return;
    }
    this.isActive = true;
    this.element.classList.add('flickity-enabled');
    if ( this.options.rightToLeft ) {
      this.element.classList.add('flickity-rtl');
    }
  
    this.getSize();
    // move initial cell elements so they can be loaded as cells
    var cellElems = this._filterFindCellElements( this.element.children );
    moveElements( cellElems, this.slider );
    this.viewport.appendChild( this.slider );
    this.element.appendChild( this.viewport );
    // get cells from children
    this.reloadCells();
  
    if ( this.options.accessibility ) {
      // allow element to focusable
      this.element.tabIndex = 0;
      // listen for key presses
      this.element.addEventListener( 'keydown', this );
    }
  
    this.emitEvent('activate');
    this.selectInitialIndex();
    // flag for initial activation, for using initialIndex
    this.isInitActivated = true;
    // ready event. #493
    this.dispatchEvent('ready');
  };
  
  // slider positions the cells
  proto._createSlider = function() {
    // slider element does all the positioning
    var slider = document.createElement('div');
    slider.className = 'flickity-slider';
    slider.style[ this.originSide ] = 0;
    this.slider = slider;
  };
  
  proto._filterFindCellElements = function( elems ) {
    return utils.filterFindElements( elems, this.options.cellSelector );
  };
  
  // goes through all children
  proto.reloadCells = function() {
    // collection of item elements
    this.cells = this._makeCells( this.slider.children );
    this.positionCells();
    this._getWrapShiftCells();
    this.setGallerySize();
  };
  
  /**
   * turn elements into Flickity.Cells
   * @param {Array or NodeList or HTMLElement} elems
   * @returns {Array} items - collection of new Flickity Cells
   */
  proto._makeCells = function( elems ) {
    var cellElems = this._filterFindCellElements( elems );
  
    // create new Flickity for collection
    var cells = cellElems.map( function( cellElem ) {
      return new Cell( cellElem, this );
    }, this );
  
    return cells;
  };
  
  proto.getLastCell = function() {
    return this.cells[ this.cells.length - 1 ];
  };
  
  proto.getLastSlide = function() {
    return this.slides[ this.slides.length - 1 ];
  };
  
  // positions all cells
  proto.positionCells = function() {
    // size all cells
    this._sizeCells( this.cells );
    // position all cells
    this._positionCells( 0 );
  };
  
  /**
   * position certain cells
   * @param {Integer} index - which cell to start with
   */
  proto._positionCells = function( index ) {
    index = index || 0;
    // also measure maxCellHeight
    // start 0 if positioning all cells
    this.maxCellHeight = index ? this.maxCellHeight || 0 : 0;
    var cellX = 0;
    // get cellX
    if ( index > 0 ) {
      var startCell = this.cells[ index - 1 ];
      cellX = startCell.x + startCell.size.outerWidth;
    }
    var len = this.cells.length;
    for ( var i=index; i < len; i++ ) {
      var cell = this.cells[i];
      cell.setPosition( cellX );
      cellX += cell.size.outerWidth;
      this.maxCellHeight = Math.max( cell.size.outerHeight, this.maxCellHeight );
    }
    // keep track of cellX for wrap-around
    this.slideableWidth = cellX;
    // slides
    this.updateSlides();
    // contain slides target
    this._containSlides();
    // update slidesWidth
    this.slidesWidth = len ? this.getLastSlide().target - this.slides[0].target : 0;
  };
  
  /**
   * cell.getSize() on multiple cells
   * @param {Array} cells
   */
  proto._sizeCells = function( cells ) {
    cells.forEach( function( cell ) {
      cell.getSize();
    });
  };
  
  // --------------------------  -------------------------- //
  
  proto.updateSlides = function() {
    this.slides = [];
    if ( !this.cells.length ) {
      return;
    }
  
    var slide = new Slide( this );
    this.slides.push( slide );
    var isOriginLeft = this.originSide == 'left';
    var nextMargin = isOriginLeft ? 'marginRight' : 'marginLeft';
  
    var canCellFit = this._getCanCellFit();
  
    this.cells.forEach( function( cell, i ) {
      // just add cell if first cell in slide
      if ( !slide.cells.length ) {
        slide.addCell( cell );
        return;
      }
  
      var slideWidth = ( slide.outerWidth - slide.firstMargin ) +
        ( cell.size.outerWidth - cell.size[ nextMargin ] );
  
      if ( canCellFit.call( this, i, slideWidth ) ) {
        slide.addCell( cell );
      } else {
        // doesn't fit, new slide
        slide.updateTarget();
  
        slide = new Slide( this );
        this.slides.push( slide );
        slide.addCell( cell );
      }
    }, this );
    // last slide
    slide.updateTarget();
    // update .selectedSlide
    this.updateSelectedSlide();
  };
  
  proto._getCanCellFit = function() {
    var groupCells = this.options.groupCells;
    if ( !groupCells ) {
      return function() {
        return false;
      };
    } else if ( typeof groupCells == 'number' ) {
      // group by number. 3 -> [0,1,2], [3,4,5], ...
      var number = parseInt( groupCells, 10 );
      return function( i ) {
        return ( i % number ) !== 0;
      };
    }
    // default, group by width of slide
    // parse '75%
    var percentMatch = typeof groupCells == 'string' &&
      groupCells.match(/^(\d+)%$/);
    var percent = percentMatch ? parseInt( percentMatch[1], 10 ) / 100 : 1;
    return function( i, slideWidth ) {
      return slideWidth <= ( this.size.innerWidth + 1 ) * percent;
    };
  };
  
  // alias _init for jQuery plugin .flickity()
  proto._init =
  proto.reposition = function() {
    this.positionCells();
    this.positionSliderAtSelected();
  };
  
  proto.getSize = function() {
    this.size = getSize( this.element );
    this.setCellAlign();
    this.cursorPosition = this.size.innerWidth * this.cellAlign;
  };
  
  var cellAlignShorthands = {
    // cell align, then based on origin side
    center: {
      left: 0.5,
      right: 0.5
    },
    left: {
      left: 0,
      right: 1
    },
    right: {
      right: 0,
      left: 1
    }
  };
  
  proto.setCellAlign = function() {
    var shorthand = cellAlignShorthands[ this.options.cellAlign ];
    this.cellAlign = shorthand ? shorthand[ this.originSide ] : this.options.cellAlign;
  };
  
  proto.setGallerySize = function() {
    if ( this.options.setGallerySize ) {
      var height = this.options.adaptiveHeight && this.selectedSlide ?
        this.selectedSlide.height : this.maxCellHeight;
      this.viewport.style.height = height + 'px';
    }
  };
  
  proto._getWrapShiftCells = function() {
    // only for wrap-around
    if ( !this.options.wrapAround ) {
      return;
    }
    // unshift previous cells
    this._unshiftCells( this.beforeShiftCells );
    this._unshiftCells( this.afterShiftCells );
    // get before cells
    // initial gap
    var gapX = this.cursorPosition;
    var cellIndex = this.cells.length - 1;
    this.beforeShiftCells = this._getGapCells( gapX, cellIndex, -1 );
    // get after cells
    // ending gap between last cell and end of gallery viewport
    gapX = this.size.innerWidth - this.cursorPosition;
    // start cloning at first cell, working forwards
    this.afterShiftCells = this._getGapCells( gapX, 0, 1 );
  };
  
  proto._getGapCells = function( gapX, cellIndex, increment ) {
    // keep adding cells until the cover the initial gap
    var cells = [];
    while ( gapX > 0 ) {
      var cell = this.cells[ cellIndex ];
      if ( !cell ) {
        break;
      }
      cells.push( cell );
      cellIndex += increment;
      gapX -= cell.size.outerWidth;
    }
    return cells;
  };
  
  // ----- contain ----- //
  
  // contain cell targets so no excess sliding
  proto._containSlides = function() {
    if ( !this.options.contain || this.options.wrapAround || !this.cells.length ) {
      return;
    }
    var isRightToLeft = this.options.rightToLeft;
    var beginMargin = isRightToLeft ? 'marginRight' : 'marginLeft';
    var endMargin = isRightToLeft ? 'marginLeft' : 'marginRight';
    var contentWidth = this.slideableWidth - this.getLastCell().size[ endMargin ];
    // content is less than gallery size
    var isContentSmaller = contentWidth < this.size.innerWidth;
    // bounds
    var beginBound = this.cursorPosition + this.cells[0].size[ beginMargin ];
    var endBound = contentWidth - this.size.innerWidth * ( 1 - this.cellAlign );
    // contain each cell target
    this.slides.forEach( function( slide ) {
      if ( isContentSmaller ) {
        // all cells fit inside gallery
        slide.target = contentWidth * this.cellAlign;
      } else {
        // contain to bounds
        slide.target = Math.max( slide.target, beginBound );
        slide.target = Math.min( slide.target, endBound );
      }
    }, this );
  };
  
  // -----  ----- //
  
  /**
   * emits events via eventEmitter and jQuery events
   * @param {String} type - name of event
   * @param {Event} event - original event
   * @param {Array} args - extra arguments
   */
  proto.dispatchEvent = function( type, event, args ) {
    var emitArgs = event ? [ event ].concat( args ) : args;
    this.emitEvent( type, emitArgs );
  
    if ( jQuery && this.$element ) {
      // default trigger with type if no event
      type += this.options.namespaceJQueryEvents ? '.flickity' : '';
      var $event = type;
      if ( event ) {
        // create jQuery event
        var jQEvent = jQuery.Event( event );
        jQEvent.type = type;
        $event = jQEvent;
      }
      this.$element.trigger( $event, args );
    }
  };
  
  // -------------------------- select -------------------------- //
  
  /**
   * @param {Integer} index - index of the slide
   * @param {Boolean} isWrap - will wrap-around to last/first if at the end
   * @param {Boolean} isInstant - will immediately set position at selected cell
   */
  proto.select = function( index, isWrap, isInstant ) {
    if ( !this.isActive ) {
      return;
    }
    index = parseInt( index, 10 );
    this._wrapSelect( index );
  
    if ( this.options.wrapAround || isWrap ) {
      index = utils.modulo( index, this.slides.length );
    }
    // bail if invalid index
    if ( !this.slides[ index ] ) {
      return;
    }
    var prevIndex = this.selectedIndex;
    this.selectedIndex = index;
    this.updateSelectedSlide();
    if ( isInstant ) {
      this.positionSliderAtSelected();
    } else {
      this.startAnimation();
    }
    if ( this.options.adaptiveHeight ) {
      this.setGallerySize();
    }
    // events
    this.dispatchEvent( 'select', null, [ index ] );
    // change event if new index
    if ( index != prevIndex ) {
      this.dispatchEvent( 'change', null, [ index ] );
    }
    // old v1 event name, remove in v3
    this.dispatchEvent('cellSelect');
  };
  
  // wraps position for wrapAround, to move to closest slide. #113
  proto._wrapSelect = function( index ) {
    var len = this.slides.length;
    var isWrapping = this.options.wrapAround && len > 1;
    if ( !isWrapping ) {
      return index;
    }
    var wrapIndex = utils.modulo( index, len );
    // go to shortest
    var delta = Math.abs( wrapIndex - this.selectedIndex );
    var backWrapDelta = Math.abs( ( wrapIndex + len ) - this.selectedIndex );
    var forewardWrapDelta = Math.abs( ( wrapIndex - len ) - this.selectedIndex );
    if ( !this.isDragSelect && backWrapDelta < delta ) {
      index += len;
    } else if ( !this.isDragSelect && forewardWrapDelta < delta ) {
      index -= len;
    }
    // wrap position so slider is within normal area
    if ( index < 0 ) {
      this.x -= this.slideableWidth;
    } else if ( index >= len ) {
      this.x += this.slideableWidth;
    }
  };
  
  proto.previous = function( isWrap, isInstant ) {
    this.select( this.selectedIndex - 1, isWrap, isInstant );
  };
  
  proto.next = function( isWrap, isInstant ) {
    this.select( this.selectedIndex + 1, isWrap, isInstant );
  };
  
  proto.updateSelectedSlide = function() {
    var slide = this.slides[ this.selectedIndex ];
    // selectedIndex could be outside of slides, if triggered before resize()
    if ( !slide ) {
      return;
    }
    // unselect previous selected slide
    this.unselectSelectedSlide();
    // update new selected slide
    this.selectedSlide = slide;
    slide.select();
    this.selectedCells = slide.cells;
    this.selectedElements = slide.getCellElements();
    // HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility
    // Remove in v3?
    this.selectedCell = slide.cells[0];
    this.selectedElement = this.selectedElements[0];
  };
  
  proto.unselectSelectedSlide = function() {
    if ( this.selectedSlide ) {
      this.selectedSlide.unselect();
    }
  };
  
  proto.selectInitialIndex = function() {
    var initialIndex = this.options.initialIndex;
    // already activated, select previous selectedIndex
    if ( this.isInitActivated ) {
      this.select( this.selectedIndex, false, true );
      return;
    }
    // select with selector string
    if ( initialIndex && typeof initialIndex == 'string' ) {
      var cell = this.queryCell( initialIndex );
      if ( cell ) {
        this.selectCell( initialIndex, false, true );
        return;
      }
    }
  
    var index = 0;
    // select with number
    if ( initialIndex && this.slides[ initialIndex ] ) {
      index = initialIndex;
    }
    // select instantly
    this.select( index, false, true );
  };
  
  /**
   * select slide from number or cell element
   * @param {Element or Number} elem
   */
  proto.selectCell = function( value, isWrap, isInstant ) {
    // get cell
    var cell = this.queryCell( value );
    if ( !cell ) {
      return;
    }
  
    var index = this.getCellSlideIndex( cell );
    this.select( index, isWrap, isInstant );
  };
  
  proto.getCellSlideIndex = function( cell ) {
    // get index of slides that has cell
    for ( var i=0; i < this.slides.length; i++ ) {
      var slide = this.slides[i];
      var index = slide.cells.indexOf( cell );
      if ( index != -1 ) {
        return i;
      }
    }
  };
  
  // -------------------------- get cells -------------------------- //
  
  /**
   * get Flickity.Cell, given an Element
   * @param {Element} elem
   * @returns {Flickity.Cell} item
   */
  proto.getCell = function( elem ) {
    // loop through cells to get the one that matches
    for ( var i=0; i < this.cells.length; i++ ) {
      var cell = this.cells[i];
      if ( cell.element == elem ) {
        return cell;
      }
    }
  };
  
  /**
   * get collection of Flickity.Cells, given Elements
   * @param {Element, Array, NodeList} elems
   * @returns {Array} cells - Flickity.Cells
   */
  proto.getCells = function( elems ) {
    elems = utils.makeArray( elems );
    var cells = [];
    elems.forEach( function( elem ) {
      var cell = this.getCell( elem );
      if ( cell ) {
        cells.push( cell );
      }
    }, this );
    return cells;
  };
  
  /**
   * get cell elements
   * @returns {Array} cellElems
   */
  proto.getCellElements = function() {
    return this.cells.map( function( cell ) {
      return cell.element;
    });
  };
  
  /**
   * get parent cell from an element
   * @param {Element} elem
   * @returns {Flickit.Cell} cell
   */
  proto.getParentCell = function( elem ) {
    // first check if elem is cell
    var cell = this.getCell( elem );
    if ( cell ) {
      return cell;
    }
    // try to get parent cell elem
    elem = utils.getParent( elem, '.flickity-slider > *' );
    return this.getCell( elem );
  };
  
  /**
   * get cells adjacent to a slide
   * @param {Integer} adjCount - number of adjacent slides
   * @param {Integer} index - index of slide to start
   * @returns {Array} cells - array of Flickity.Cells
   */
  proto.getAdjacentCellElements = function( adjCount, index ) {
    if ( !adjCount ) {
      return this.selectedSlide.getCellElements();
    }
    index = index === undefined ? this.selectedIndex : index;
  
    var len = this.slides.length;
    if ( 1 + ( adjCount * 2 ) >= len ) {
      return this.getCellElements();
    }
  
    var cellElems = [];
    for ( var i = index - adjCount; i <= index + adjCount ; i++ ) {
      var slideIndex = this.options.wrapAround ? utils.modulo( i, len ) : i;
      var slide = this.slides[ slideIndex ];
      if ( slide ) {
        cellElems = cellElems.concat( slide.getCellElements() );
      }
    }
    return cellElems;
  };
  
  /**
   * select slide from number or cell element
   * @param {Element, Selector String, or Number} selector
   */
  proto.queryCell = function( selector ) {
    if ( typeof selector == 'number' ) {
      // use number as index
      return this.cells[ selector ];
    }
    if ( typeof selector == 'string' ) {
      // do not select invalid selectors from hash: #123, #/. #791
      if ( selector.match(/^[#\.]?[\d\/]/) ) {
        return;
      }
      // use string as selector, get element
      selector = this.element.querySelector( selector );
    }
    // get cell from element
    return this.getCell( selector );
  };
  
  // -------------------------- events -------------------------- //
  
  proto.uiChange = function() {
    this.emitEvent('uiChange');
  };
  
  // keep focus on element when child UI elements are clicked
  proto.childUIPointerDown = function( event ) {
    // HACK iOS does not allow touch events to bubble up?!
    if ( event.type != 'touchstart' ) {
      event.preventDefault();
    }
    this.focus();
  };
  
  // ----- resize ----- //
  
  proto.onresize = function() {
    this.watchCSS();
    this.resize();
  };
  
  utils.debounceMethod( Flickity, 'onresize', 150 );
  
  proto.resize = function() {
    if ( !this.isActive ) {
      return;
    }
    this.getSize();
    // wrap values
    if ( this.options.wrapAround ) {
      this.x = utils.modulo( this.x, this.slideableWidth );
    }
    this.positionCells();
    this._getWrapShiftCells();
    this.setGallerySize();
    this.emitEvent('resize');
    // update selected index for group slides, instant
    // TODO: position can be lost between groups of various numbers
    var selectedElement = this.selectedElements && this.selectedElements[0];
    this.selectCell( selectedElement, false, true );
  };
  
  // watches the :after property, activates/deactivates
  proto.watchCSS = function() {
    var watchOption = this.options.watchCSS;
    if ( !watchOption ) {
      return;
    }
  
    var afterContent = getComputedStyle( this.element, ':after' ).content;
    // activate if :after { content: 'flickity' }
    if ( afterContent.indexOf('flickity') != -1 ) {
      this.activate();
    } else {
      this.deactivate();
    }
  };
  
  // ----- keydown ----- //
  
  // go previous/next if left/right keys pressed
  proto.onkeydown = function( event ) {
    // only work if element is in focus
    var isNotFocused = document.activeElement && document.activeElement != this.element;
    if ( !this.options.accessibility ||isNotFocused ) {
      return;
    }
  
    var handler = Flickity.keyboardHandlers[ event.keyCode ];
    if ( handler ) {
      handler.call( this );
    }
  };
  
  Flickity.keyboardHandlers = {
    // left arrow
    37: function() {
      var leftMethod = this.options.rightToLeft ? 'next' : 'previous';
      this.uiChange();
      this[ leftMethod ]();
    },
    // right arrow
    39: function() {
      var rightMethod = this.options.rightToLeft ? 'previous' : 'next';
      this.uiChange();
      this[ rightMethod ]();
    },
  };
  
  // ----- focus ----- //
  
  proto.focus = function() {
    // TODO remove scrollTo once focus options gets more support
    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#Browser_compatibility
    var prevScrollY = window.pageYOffset;
    this.element.focus({ preventScroll: true });
    // hack to fix scroll jump after focus, #76
    if ( window.pageYOffset != prevScrollY ) {
      window.scrollTo( window.pageXOffset, prevScrollY );
    }
  };
  
  // -------------------------- destroy -------------------------- //
  
  // deactivate all Flickity functionality, but keep stuff available
  proto.deactivate = function() {
    if ( !this.isActive ) {
      return;
    }
    this.element.classList.remove('flickity-enabled');
    this.element.classList.remove('flickity-rtl');
    this.unselectSelectedSlide();
    // destroy cells
    this.cells.forEach( function( cell ) {
      cell.destroy();
    });
    this.element.removeChild( this.viewport );
    // move child elements back into element
    moveElements( this.slider.children, this.element );
    if ( this.options.accessibility ) {
      this.element.removeAttribute('tabIndex');
      this.element.removeEventListener( 'keydown', this );
    }
    // set flags
    this.isActive = false;
    this.emitEvent('deactivate');
  };
  
  proto.destroy = function() {
    this.deactivate();
    window.removeEventListener( 'resize', this );
    this.allOff();
    this.emitEvent('destroy');
    if ( jQuery && this.$element ) {
      jQuery.removeData( this.element, 'flickity' );
    }
    delete this.element.flickityGUID;
    delete instances[ this.guid ];
  };
  
  // -------------------------- prototype -------------------------- //
  
  utils.extend( proto, animatePrototype );
  
  // -------------------------- extras -------------------------- //
  
  /**
   * get Flickity instance from element
   * @param {Element} elem
   * @returns {Flickity}
   */
  Flickity.data = function( elem ) {
    elem = utils.getQueryElement( elem );
    var id = elem && elem.flickityGUID;
    return id && instances[ id ];
  };
  
  utils.htmlInit( Flickity, 'flickity' );
  
  if ( jQuery && jQuery.bridget ) {
    jQuery.bridget( 'flickity', Flickity );
  }
  
  // set internal jQuery, for Webpack + jQuery v3, #478
  Flickity.setJQuery = function( jq ) {
    jQuery = jq;
  };
  
  Flickity.Cell = Cell;
  Flickity.Slide = Slide;
  
  return Flickity;
  
  }));
  
  /*!
   * Unipointer v2.3.0
   * base class for doing one thing with pointer event
   * MIT license
   */
  
  /*jshint browser: true, undef: true, unused: true, strict: true */
  
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */ /*global define, module, require */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'unipointer/unipointer',[
        'ev-emitter/ev-emitter'
      ], function( EvEmitter ) {
        return factory( window, EvEmitter );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('ev-emitter')
      );
    } else {
      // browser global
      window.Unipointer = factory(
        window,
        window.EvEmitter
      );
    }
  
  }( window, function factory( window, EvEmitter ) {
  
  
  
  function noop() {}
  
  function Unipointer() {}
  
  // inherit EvEmitter
  var proto = Unipointer.prototype = Object.create( EvEmitter.prototype );
  
  proto.bindStartEvent = function( elem ) {
    this._bindStartEvent( elem, true );
  };
  
  proto.unbindStartEvent = function( elem ) {
    this._bindStartEvent( elem, false );
  };
  
  /**
   * Add or remove start event
   * @param {Boolean} isAdd - remove if falsey
   */
  proto._bindStartEvent = function( elem, isAdd ) {
    // munge isAdd, default to true
    isAdd = isAdd === undefined ? true : isAdd;
    var bindMethod = isAdd ? 'addEventListener' : 'removeEventListener';
  
    // default to mouse events
    var startEvent = 'mousedown';
    if ( window.PointerEvent ) {
      // Pointer Events
      startEvent = 'pointerdown';
    } else if ( 'ontouchstart' in window ) {
      // Touch Events. iOS Safari
      startEvent = 'touchstart';
    }
    elem[ bindMethod ]( startEvent, this );
  };
  
  // trigger handler methods for events
  proto.handleEvent = function( event ) {
    var method = 'on' + event.type;
    if ( this[ method ] ) {
      this[ method ]( event );
    }
  };
  
  // returns the touch that we're keeping track of
  proto.getTouch = function( touches ) {
    for ( var i=0; i < touches.length; i++ ) {
      var touch = touches[i];
      if ( touch.identifier == this.pointerIdentifier ) {
        return touch;
      }
    }
  };
  
  // ----- start event ----- //
  
  proto.onmousedown = function( event ) {
    // dismiss clicks from right or middle buttons
    var button = event.button;
    if ( button && ( button !== 0 && button !== 1 ) ) {
      return;
    }
    this._pointerDown( event, event );
  };
  
  proto.ontouchstart = function( event ) {
    this._pointerDown( event, event.changedTouches[0] );
  };
  
  proto.onpointerdown = function( event ) {
    this._pointerDown( event, event );
  };
  
  /**
   * pointer start
   * @param {Event} event
   * @param {Event or Touch} pointer
   */
  proto._pointerDown = function( event, pointer ) {
    // dismiss right click and other pointers
    // button = 0 is okay, 1-4 not
    if ( event.button || this.isPointerDown ) {
      return;
    }
  
    this.isPointerDown = true;
    // save pointer identifier to match up touch events
    this.pointerIdentifier = pointer.pointerId !== undefined ?
      // pointerId for pointer events, touch.indentifier for touch events
      pointer.pointerId : pointer.identifier;
  
    this.pointerDown( event, pointer );
  };
  
  proto.pointerDown = function( event, pointer ) {
    this._bindPostStartEvents( event );
    this.emitEvent( 'pointerDown', [ event, pointer ] );
  };
  
  // hash of events to be bound after start event
  var postStartEvents = {
    mousedown: [ 'mousemove', 'mouseup' ],
    touchstart: [ 'touchmove', 'touchend', 'touchcancel' ],
    pointerdown: [ 'pointermove', 'pointerup', 'pointercancel' ],
  };
  
  proto._bindPostStartEvents = function( event ) {
    if ( !event ) {
      return;
    }
    // get proper events to match start event
    var events = postStartEvents[ event.type ];
    // bind events to node
    events.forEach( function( eventName ) {
      window.addEventListener( eventName, this );
    }, this );
    // save these arguments
    this._boundPointerEvents = events;
  };
  
  proto._unbindPostStartEvents = function() {
    // check for _boundEvents, in case dragEnd triggered twice (old IE8 bug)
    if ( !this._boundPointerEvents ) {
      return;
    }
    this._boundPointerEvents.forEach( function( eventName ) {
      window.removeEventListener( eventName, this );
    }, this );
  
    delete this._boundPointerEvents;
  };
  
  // ----- move event ----- //
  
  proto.onmousemove = function( event ) {
    this._pointerMove( event, event );
  };
  
  proto.onpointermove = function( event ) {
    if ( event.pointerId == this.pointerIdentifier ) {
      this._pointerMove( event, event );
    }
  };
  
  proto.ontouchmove = function( event ) {
    var touch = this.getTouch( event.changedTouches );
    if ( touch ) {
      this._pointerMove( event, touch );
    }
  };
  
  /**
   * pointer move
   * @param {Event} event
   * @param {Event or Touch} pointer
   * @private
   */
  proto._pointerMove = function( event, pointer ) {
    this.pointerMove( event, pointer );
  };
  
  // public
  proto.pointerMove = function( event, pointer ) {
    this.emitEvent( 'pointerMove', [ event, pointer ] );
  };
  
  // ----- end event ----- //
  
  
  proto.onmouseup = function( event ) {
    this._pointerUp( event, event );
  };
  
  proto.onpointerup = function( event ) {
    if ( event.pointerId == this.pointerIdentifier ) {
      this._pointerUp( event, event );
    }
  };
  
  proto.ontouchend = function( event ) {
    var touch = this.getTouch( event.changedTouches );
    if ( touch ) {
      this._pointerUp( event, touch );
    }
  };
  
  /**
   * pointer up
   * @param {Event} event
   * @param {Event or Touch} pointer
   * @private
   */
  proto._pointerUp = function( event, pointer ) {
    this._pointerDone();
    this.pointerUp( event, pointer );
  };
  
  // public
  proto.pointerUp = function( event, pointer ) {
    this.emitEvent( 'pointerUp', [ event, pointer ] );
  };
  
  // ----- pointer done ----- //
  
  // triggered on pointer up & pointer cancel
  proto._pointerDone = function() {
    this._pointerReset();
    this._unbindPostStartEvents();
    this.pointerDone();
  };
  
  proto._pointerReset = function() {
    // reset properties
    this.isPointerDown = false;
    delete this.pointerIdentifier;
  };
  
  proto.pointerDone = noop;
  
  // ----- pointer cancel ----- //
  
  proto.onpointercancel = function( event ) {
    if ( event.pointerId == this.pointerIdentifier ) {
      this._pointerCancel( event, event );
    }
  };
  
  proto.ontouchcancel = function( event ) {
    var touch = this.getTouch( event.changedTouches );
    if ( touch ) {
      this._pointerCancel( event, touch );
    }
  };
  
  /**
   * pointer cancel
   * @param {Event} event
   * @param {Event or Touch} pointer
   * @private
   */
  proto._pointerCancel = function( event, pointer ) {
    this._pointerDone();
    this.pointerCancel( event, pointer );
  };
  
  // public
  proto.pointerCancel = function( event, pointer ) {
    this.emitEvent( 'pointerCancel', [ event, pointer ] );
  };
  
  // -----  ----- //
  
  // utility function for getting x/y coords from event
  Unipointer.getPointerPoint = function( pointer ) {
    return {
      x: pointer.pageX,
      y: pointer.pageY
    };
  };
  
  // -----  ----- //
  
  return Unipointer;
  
  }));
  
  /*!
   * Unidragger v2.3.0
   * Draggable base class
   * MIT license
   */
  
  /*jshint browser: true, unused: true, undef: true, strict: true */
  
  ( function( window, factory ) {
    // universal module definition
    /*jshint strict: false */ /*globals define, module, require */
  
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'unidragger/unidragger',[
        'unipointer/unipointer'
      ], function( Unipointer ) {
        return factory( window, Unipointer );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('unipointer')
      );
    } else {
      // browser global
      window.Unidragger = factory(
        window,
        window.Unipointer
      );
    }
  
  }( window, function factory( window, Unipointer ) {
  
  
  
  // -------------------------- Unidragger -------------------------- //
  
  function Unidragger() {}
  
  // inherit Unipointer & EvEmitter
  var proto = Unidragger.prototype = Object.create( Unipointer.prototype );
  
  // ----- bind start ----- //
  
  proto.bindHandles = function() {
    this._bindHandles( true );
  };
  
  proto.unbindHandles = function() {
    this._bindHandles( false );
  };
  
  /**
   * Add or remove start event
   * @param {Boolean} isAdd
   */
  proto._bindHandles = function( isAdd ) {
    // munge isAdd, default to true
    isAdd = isAdd === undefined ? true : isAdd;
    // bind each handle
    var bindMethod = isAdd ? 'addEventListener' : 'removeEventListener';
    var touchAction = isAdd ? this._touchActionValue : '';
    for ( var i=0; i < this.handles.length; i++ ) {
      var handle = this.handles[i];
      this._bindStartEvent( handle, isAdd );
      handle[ bindMethod ]( 'click', this );
      // touch-action: none to override browser touch gestures. metafizzy/flickity#540
      if ( window.PointerEvent ) {
        handle.style.touchAction = touchAction;
      }
    }
  };
  
  // prototype so it can be overwriteable by Flickity
  proto._touchActionValue = 'none';
  
  // ----- start event ----- //
  
  /**
   * pointer start
   * @param {Event} event
   * @param {Event or Touch} pointer
   */
  proto.pointerDown = function( event, pointer ) {
    var isOkay = this.okayPointerDown( event );
    if ( !isOkay ) {
      return;
    }
    // track start event position
    this.pointerDownPointer = pointer;
  
    event.preventDefault();
    this.pointerDownBlur();
    // bind move and end events
    this._bindPostStartEvents( event );
    this.emitEvent( 'pointerDown', [ event, pointer ] );
  };
  
  // nodes that have text fields
  var cursorNodes = {
    TEXTAREA: true,
    INPUT: true,
    SELECT: true,
    OPTION: true,
  };
  
  // input types that do not have text fields
  var clickTypes = {
    radio: true,
    checkbox: true,
    button: true,
    submit: true,
    image: true,
    file: true,
  };
  
  // dismiss inputs with text fields. flickity#403, flickity#404
  proto.okayPointerDown = function( event ) {
    var isCursorNode = cursorNodes[ event.target.nodeName ];
    var isClickType = clickTypes[ event.target.type ];
    var isOkay = !isCursorNode || isClickType;
    if ( !isOkay ) {
      this._pointerReset();
    }
    return isOkay;
  };
  
  // kludge to blur previously focused input
  proto.pointerDownBlur = function() {
    var focused = document.activeElement;
    // do not blur body for IE10, metafizzy/flickity#117
    var canBlur = focused && focused.blur && focused != document.body;
    if ( canBlur ) {
      focused.blur();
    }
  };
  
  // ----- move event ----- //
  
  /**
   * drag move
   * @param {Event} event
   * @param {Event or Touch} pointer
   */
  proto.pointerMove = function( event, pointer ) {
    var moveVector = this._dragPointerMove( event, pointer );
    this.emitEvent( 'pointerMove', [ event, pointer, moveVector ] );
    this._dragMove( event, pointer, moveVector );
  };
  
  // base pointer move logic
  proto._dragPointerMove = function( event, pointer ) {
    var moveVector = {
      x: pointer.pageX - this.pointerDownPointer.pageX,
      y: pointer.pageY - this.pointerDownPointer.pageY
    };
    // start drag if pointer has moved far enough to start drag
    if ( !this.isDragging && this.hasDragStarted( moveVector ) ) {
      this._dragStart( event, pointer );
    }
    return moveVector;
  };
  
  // condition if pointer has moved far enough to start drag
  proto.hasDragStarted = function( moveVector ) {
    return Math.abs( moveVector.x ) > 3 || Math.abs( moveVector.y ) > 3;
  };
  
  // ----- end event ----- //
  
  /**
   * pointer up
   * @param {Event} event
   * @param {Event or Touch} pointer
   */
  proto.pointerUp = function( event, pointer ) {
    this.emitEvent( 'pointerUp', [ event, pointer ] );
    this._dragPointerUp( event, pointer );
  };
  
  proto._dragPointerUp = function( event, pointer ) {
    if ( this.isDragging ) {
      this._dragEnd( event, pointer );
    } else {
      // pointer didn't move enough for drag to start
      this._staticClick( event, pointer );
    }
  };
  
  // -------------------------- drag -------------------------- //
  
  // dragStart
  proto._dragStart = function( event, pointer ) {
    this.isDragging = true;
    // prevent clicks
    this.isPreventingClicks = true;
    this.dragStart( event, pointer );
  };
  
  proto.dragStart = function( event, pointer ) {
    this.emitEvent( 'dragStart', [ event, pointer ] );
  };
  
  // dragMove
  proto._dragMove = function( event, pointer, moveVector ) {
    // do not drag if not dragging yet
    if ( !this.isDragging ) {
      return;
    }
  
    this.dragMove( event, pointer, moveVector );
  };
  
  proto.dragMove = function( event, pointer, moveVector ) {
    event.preventDefault();
    this.emitEvent( 'dragMove', [ event, pointer, moveVector ] );
  };
  
  // dragEnd
  proto._dragEnd = function( event, pointer ) {
    // set flags
    this.isDragging = false;
    // re-enable clicking async
    setTimeout( function() {
      delete this.isPreventingClicks;
    }.bind( this ) );
  
    this.dragEnd( event, pointer );
  };
  
  proto.dragEnd = function( event, pointer ) {
    this.emitEvent( 'dragEnd', [ event, pointer ] );
  };
  
  // ----- onclick ----- //
  
  // handle all clicks and prevent clicks when dragging
  proto.onclick = function( event ) {
    if ( this.isPreventingClicks ) {
      event.preventDefault();
    }
  };
  
  // ----- staticClick ----- //
  
  // triggered after pointer down & up with no/tiny movement
  proto._staticClick = function( event, pointer ) {
    // ignore emulated mouse up clicks
    if ( this.isIgnoringMouseUp && event.type == 'mouseup' ) {
      return;
    }
  
    this.staticClick( event, pointer );
  
    // set flag for emulated clicks 300ms after touchend
    if ( event.type != 'mouseup' ) {
      this.isIgnoringMouseUp = true;
      // reset flag after 300ms
      setTimeout( function() {
        delete this.isIgnoringMouseUp;
      }.bind( this ), 400 );
    }
  };
  
  proto.staticClick = function( event, pointer ) {
    this.emitEvent( 'staticClick', [ event, pointer ] );
  };
  
  // ----- utils ----- //
  
  Unidragger.getPointerPoint = Unipointer.getPointerPoint;
  
  // -----  ----- //
  
  return Unidragger;
  
  }));
  
  // drag
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/drag',[
        './flickity',
        'unidragger/unidragger',
        'fizzy-ui-utils/utils'
      ], function( Flickity, Unidragger, utils ) {
        return factory( window, Flickity, Unidragger, utils );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('./flickity'),
        require('unidragger'),
        require('fizzy-ui-utils')
      );
    } else {
      // browser global
      window.Flickity = factory(
        window,
        window.Flickity,
        window.Unidragger,
        window.fizzyUIUtils
      );
    }
  
  }( window, function factory( window, Flickity, Unidragger, utils ) {
  
  
  
  // ----- defaults ----- //
  
  utils.extend( Flickity.defaults, {
    draggable: '>1',
    dragThreshold: 3,
  });
  
  // ----- create ----- //
  
  Flickity.createMethods.push('_createDrag');
  
  // -------------------------- drag prototype -------------------------- //
  
  var proto = Flickity.prototype;
  utils.extend( proto, Unidragger.prototype );
  proto._touchActionValue = 'pan-y';
  
  // --------------------------  -------------------------- //
  
  var isTouch = 'createTouch' in document;
  var isTouchmoveScrollCanceled = false;
  
  proto._createDrag = function() {
    this.on( 'activate', this.onActivateDrag );
    this.on( 'uiChange', this._uiChangeDrag );
    this.on( 'deactivate', this.onDeactivateDrag );
    this.on( 'cellChange', this.updateDraggable );
    // TODO updateDraggable on resize? if groupCells & slides change
    // HACK - add seemingly innocuous handler to fix iOS 10 scroll behavior
    // #457, RubaXa/Sortable#973
    if ( isTouch && !isTouchmoveScrollCanceled ) {
      window.addEventListener( 'touchmove', function() {});
      isTouchmoveScrollCanceled = true;
    }
  };
  
  proto.onActivateDrag = function() {
    this.handles = [ this.viewport ];
    this.bindHandles();
    this.updateDraggable();
  };
  
  proto.onDeactivateDrag = function() {
    this.unbindHandles();
    this.element.classList.remove('is-draggable');
  };
  
  proto.updateDraggable = function() {
    // disable dragging if less than 2 slides. #278
    if ( this.options.draggable == '>1' ) {
      this.isDraggable = this.slides.length > 1;
    } else {
      this.isDraggable = this.options.draggable;
    }
    if ( this.isDraggable ) {
      this.element.classList.add('is-draggable');
    } else {
      this.element.classList.remove('is-draggable');
    }
  };
  
  // backwards compatibility
  proto.bindDrag = function() {
    this.options.draggable = true;
    this.updateDraggable();
  };
  
  proto.unbindDrag = function() {
    this.options.draggable = false;
    this.updateDraggable();
  };
  
  proto._uiChangeDrag = function() {
    delete this.isFreeScrolling;
  };
  
  // -------------------------- pointer events -------------------------- //
  
  proto.pointerDown = function( event, pointer ) {
    if ( !this.isDraggable ) {
      this._pointerDownDefault( event, pointer );
      return;
    }
    var isOkay = this.okayPointerDown( event );
    if ( !isOkay ) {
      return;
    }
  
    this._pointerDownPreventDefault( event );
    this.pointerDownFocus( event );
    // blur
    if ( document.activeElement != this.element ) {
      // do not blur if already focused
      this.pointerDownBlur();
    }
  
    // stop if it was moving
    this.dragX = this.x;
    this.viewport.classList.add('is-pointer-down');
    // track scrolling
    this.pointerDownScroll = getScrollPosition();
    window.addEventListener( 'scroll', this );
  
    this._pointerDownDefault( event, pointer );
  };
  
  // default pointerDown logic, used for staticClick
  proto._pointerDownDefault = function( event, pointer ) {
    // track start event position
    // Safari 9 overrides pageX and pageY. These values needs to be copied. #779
    this.pointerDownPointer = {
      pageX: pointer.pageX,
      pageY: pointer.pageY,
    };
    // bind move and end events
    this._bindPostStartEvents( event );
    this.dispatchEvent( 'pointerDown', event, [ pointer ] );
  };
  
  var focusNodes = {
    INPUT: true,
    TEXTAREA: true,
    SELECT: true,
  };
  
  proto.pointerDownFocus = function( event ) {
    var isFocusNode = focusNodes[ event.target.nodeName ];
    if ( !isFocusNode ) {
      this.focus();
    }
  };
  
  proto._pointerDownPreventDefault = function( event ) {
    var isTouchStart = event.type == 'touchstart';
    var isTouchPointer = event.pointerType == 'touch';
    var isFocusNode = focusNodes[ event.target.nodeName ];
    if ( !isTouchStart && !isTouchPointer && !isFocusNode ) {
      event.preventDefault();
    }
  };
  
  // ----- move ----- //
  
  proto.hasDragStarted = function( moveVector ) {
    return Math.abs( moveVector.x ) > this.options.dragThreshold;
  };
  
  // ----- up ----- //
  
  proto.pointerUp = function( event, pointer ) {
    delete this.isTouchScrolling;
    this.viewport.classList.remove('is-pointer-down');
    this.dispatchEvent( 'pointerUp', event, [ pointer ] );
    this._dragPointerUp( event, pointer );
  };
  
  proto.pointerDone = function() {
    window.removeEventListener( 'scroll', this );
    delete this.pointerDownScroll;
  };
  
  // -------------------------- dragging -------------------------- //
  
  proto.dragStart = function( event, pointer ) {
    if ( !this.isDraggable ) {
      return;
    }
    this.dragStartPosition = this.x;
    this.startAnimation();
    window.removeEventListener( 'scroll', this );
    this.dispatchEvent( 'dragStart', event, [ pointer ] );
  };
  
  proto.pointerMove = function( event, pointer ) {
    var moveVector = this._dragPointerMove( event, pointer );
    this.dispatchEvent( 'pointerMove', event, [ pointer, moveVector ] );
    this._dragMove( event, pointer, moveVector );
  };
  
  proto.dragMove = function( event, pointer, moveVector ) {
    if ( !this.isDraggable ) {
      return;
    }
    event.preventDefault();
  
    this.previousDragX = this.dragX;
    // reverse if right-to-left
    var direction = this.options.rightToLeft ? -1 : 1;
    if ( this.options.wrapAround ) {
      // wrap around move. #589
      moveVector.x = moveVector.x % this.slideableWidth;
    }
    var dragX = this.dragStartPosition + moveVector.x * direction;
  
    if ( !this.options.wrapAround && this.slides.length ) {
      // slow drag
      var originBound = Math.max( -this.slides[0].target, this.dragStartPosition );
      dragX = dragX > originBound ? ( dragX + originBound ) * 0.5 : dragX;
      var endBound = Math.min( -this.getLastSlide().target, this.dragStartPosition );
      dragX = dragX < endBound ? ( dragX + endBound ) * 0.5 : dragX;
    }
  
    this.dragX = dragX;
  
    this.dragMoveTime = new Date();
    this.dispatchEvent( 'dragMove', event, [ pointer, moveVector ] );
  };
  
  proto.dragEnd = function( event, pointer ) {
    if ( !this.isDraggable ) {
      return;
    }
    if ( this.options.freeScroll ) {
      this.isFreeScrolling = true;
    }
    // set selectedIndex based on where flick will end up
    var index = this.dragEndRestingSelect();
  
    if ( this.options.freeScroll && !this.options.wrapAround ) {
      // if free-scroll & not wrap around
      // do not free-scroll if going outside of bounding slides
      // so bounding slides can attract slider, and keep it in bounds
      var restingX = this.getRestingPosition();
      this.isFreeScrolling = -restingX > this.slides[0].target &&
        -restingX < this.getLastSlide().target;
    } else if ( !this.options.freeScroll && index == this.selectedIndex ) {
      // boost selection if selected index has not changed
      index += this.dragEndBoostSelect();
    }
    delete this.previousDragX;
    // apply selection
    // TODO refactor this, selecting here feels weird
    // HACK, set flag so dragging stays in correct direction
    this.isDragSelect = this.options.wrapAround;
    this.select( index );
    delete this.isDragSelect;
    this.dispatchEvent( 'dragEnd', event, [ pointer ] );
  };
  
  proto.dragEndRestingSelect = function() {
    var restingX = this.getRestingPosition();
    // how far away from selected slide
    var distance = Math.abs( this.getSlideDistance( -restingX, this.selectedIndex ) );
    // get closet resting going up and going down
    var positiveResting = this._getClosestResting( restingX, distance, 1 );
    var negativeResting = this._getClosestResting( restingX, distance, -1 );
    // use closer resting for wrap-around
    var index = positiveResting.distance < negativeResting.distance ?
      positiveResting.index : negativeResting.index;
    return index;
  };
  
  /**
   * given resting X and distance to selected cell
   * get the distance and index of the closest cell
   * @param {Number} restingX - estimated post-flick resting position
   * @param {Number} distance - distance to selected cell
   * @param {Integer} increment - +1 or -1, going up or down
   * @returns {Object} - { distance: {Number}, index: {Integer} }
   */
  proto._getClosestResting = function( restingX, distance, increment ) {
    var index = this.selectedIndex;
    var minDistance = Infinity;
    var condition = this.options.contain && !this.options.wrapAround ?
      // if contain, keep going if distance is equal to minDistance
      function( d, md ) { return d <= md; } : function( d, md ) { return d < md; };
    while ( condition( distance, minDistance ) ) {
      // measure distance to next cell
      index += increment;
      minDistance = distance;
      distance = this.getSlideDistance( -restingX, index );
      if ( distance === null ) {
        break;
      }
      distance = Math.abs( distance );
    }
    return {
      distance: minDistance,
      // selected was previous index
      index: index - increment
    };
  };
  
  /**
   * measure distance between x and a slide target
   * @param {Number} x
   * @param {Integer} index - slide index
   */
  proto.getSlideDistance = function( x, index ) {
    var len = this.slides.length;
    // wrap around if at least 2 slides
    var isWrapAround = this.options.wrapAround && len > 1;
    var slideIndex = isWrapAround ? utils.modulo( index, len ) : index;
    var slide = this.slides[ slideIndex ];
    if ( !slide ) {
      return null;
    }
    // add distance for wrap-around slides
    var wrap = isWrapAround ? this.slideableWidth * Math.floor( index / len ) : 0;
    return x - ( slide.target + wrap );
  };
  
  proto.dragEndBoostSelect = function() {
    // do not boost if no previousDragX or dragMoveTime
    if ( this.previousDragX === undefined || !this.dragMoveTime ||
      // or if drag was held for 100 ms
      new Date() - this.dragMoveTime > 100 ) {
      return 0;
    }
  
    var distance = this.getSlideDistance( -this.dragX, this.selectedIndex );
    var delta = this.previousDragX - this.dragX;
    if ( distance > 0 && delta > 0 ) {
      // boost to next if moving towards the right, and positive velocity
      return 1;
    } else if ( distance < 0 && delta < 0 ) {
      // boost to previous if moving towards the left, and negative velocity
      return -1;
    }
    return 0;
  };
  
  // ----- staticClick ----- //
  
  proto.staticClick = function( event, pointer ) {
    // get clickedCell, if cell was clicked
    var clickedCell = this.getParentCell( event.target );
    var cellElem = clickedCell && clickedCell.element;
    var cellIndex = clickedCell && this.cells.indexOf( clickedCell );
    this.dispatchEvent( 'staticClick', event, [ pointer, cellElem, cellIndex ] );
  };
  
  // ----- scroll ----- //
  
  proto.onscroll = function() {
    var scroll = getScrollPosition();
    var scrollMoveX = this.pointerDownScroll.x - scroll.x;
    var scrollMoveY = this.pointerDownScroll.y - scroll.y;
    // cancel click/tap if scroll is too much
    if ( Math.abs( scrollMoveX ) > 3 || Math.abs( scrollMoveY ) > 3 ) {
      this._pointerDone();
    }
  };
  
  // ----- utils ----- //
  
  function getScrollPosition() {
    return {
      x: window.pageXOffset,
      y: window.pageYOffset
    };
  }
  
  // -----  ----- //
  
  return Flickity;
  
  }));
  
  // prev/next buttons
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/prev-next-button',[
        './flickity',
        'unipointer/unipointer',
        'fizzy-ui-utils/utils'
      ], function( Flickity, Unipointer, utils ) {
        return factory( window, Flickity, Unipointer, utils );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('./flickity'),
        require('unipointer'),
        require('fizzy-ui-utils')
      );
    } else {
      // browser global
      factory(
        window,
        window.Flickity,
        window.Unipointer,
        window.fizzyUIUtils
      );
    }
  
  }( window, function factory( window, Flickity, Unipointer, utils ) {
  'use strict';
  
  var svgURI = 'http://www.w3.org/2000/svg';
  
  // -------------------------- PrevNextButton -------------------------- //
  
  function PrevNextButton( direction, parent ) {
    this.direction = direction;
    this.parent = parent;
    this._create();
  }
  
  PrevNextButton.prototype = Object.create( Unipointer.prototype );
  
  PrevNextButton.prototype._create = function() {
    // properties
    this.isEnabled = true;
    this.isPrevious = this.direction == -1;
    var leftDirection = this.parent.options.rightToLeft ? 1 : -1;
    this.isLeft = this.direction == leftDirection;
  
    var element = this.element = document.createElement('button');
    element.className = 'flickity-button flickity-prev-next-button';
    element.className += this.isPrevious ? ' previous' : ' next';
    // prevent button from submitting form http://stackoverflow.com/a/10836076/182183
    element.setAttribute( 'type', 'button' );
    // init as disabled
    this.disable();
  
    element.setAttribute( 'aria-label', this.isPrevious ? 'Previous' : 'Next' );
  
    // create arrow
    var svg = this.createSVG();
    element.appendChild( svg );
    // events
    this.parent.on( 'select', this.update.bind( this ) );
    this.on( 'pointerDown', this.parent.childUIPointerDown.bind( this.parent ) );
  };
  
  PrevNextButton.prototype.activate = function() {
    this.bindStartEvent( this.element );
    this.element.addEventListener( 'click', this );
    // add to DOM
    this.parent.element.appendChild( this.element );
  };
  
  PrevNextButton.prototype.deactivate = function() {
    // remove from DOM
    this.parent.element.removeChild( this.element );
    // click events
    this.unbindStartEvent( this.element );
    this.element.removeEventListener( 'click', this );
  };
  
  PrevNextButton.prototype.createSVG = function() {
    var svg = document.createElementNS( svgURI, 'svg');
    svg.setAttribute( 'class', 'flickity-button-icon' );
    svg.setAttribute( 'viewBox', '0 0 100 100' );
    var path = document.createElementNS( svgURI, 'path');
    var pathMovements = getArrowMovements( this.parent.options.arrowShape );
    path.setAttribute( 'd', pathMovements );
    path.setAttribute( 'class', 'arrow' );
    // rotate arrow
    if ( !this.isLeft ) {
      path.setAttribute( 'transform', 'translate(100, 100) rotate(180) ' );
    }
    svg.appendChild( path );
    return svg;
  };
  
  // get SVG path movmement
  function getArrowMovements( shape ) {
    // use shape as movement if string
    if ( typeof shape == 'string' ) {
      return shape;
    }
    // create movement string
    return 'M ' + shape.x0 + ',50' +
      ' L ' + shape.x1 + ',' + ( shape.y1 + 50 ) +
      ' L ' + shape.x2 + ',' + ( shape.y2 + 50 ) +
      ' L ' + shape.x3 + ',50 ' +
      ' L ' + shape.x2 + ',' + ( 50 - shape.y2 ) +
      ' L ' + shape.x1 + ',' + ( 50 - shape.y1 ) +
      ' Z';
  }
  
  PrevNextButton.prototype.handleEvent = utils.handleEvent;
  
  PrevNextButton.prototype.onclick = function() {
    if ( !this.isEnabled ) {
      return;
    }
    this.parent.uiChange();
    var method = this.isPrevious ? 'previous' : 'next';
    this.parent[ method ]();
  };
  
  // -----  ----- //
  
  PrevNextButton.prototype.enable = function() {
    if ( this.isEnabled ) {
      return;
    }
    this.element.disabled = false;
    this.isEnabled = true;
  };
  
  PrevNextButton.prototype.disable = function() {
    if ( !this.isEnabled ) {
      return;
    }
    this.element.disabled = true;
    this.isEnabled = false;
  };
  
  PrevNextButton.prototype.update = function() {
    // index of first or last slide, if previous or next
    var slides = this.parent.slides;
    // enable is wrapAround and at least 2 slides
    if ( this.parent.options.wrapAround && slides.length > 1 ) {
      this.enable();
      return;
    }
    var lastIndex = slides.length ? slides.length - 1 : 0;
    var boundIndex = this.isPrevious ? 0 : lastIndex;
    var method = this.parent.selectedIndex == boundIndex ? 'disable' : 'enable';
    this[ method ]();
  };
  
  PrevNextButton.prototype.destroy = function() {
    this.deactivate();
    this.allOff();
  };
  
  // -------------------------- Flickity prototype -------------------------- //
  
  utils.extend( Flickity.defaults, {
    prevNextButtons: true,
    arrowShape: {
      x0: 10,
      x1: 60, y1: 50,
      x2: 70, y2: 40,
      x3: 30
    }
  });
  
  Flickity.createMethods.push('_createPrevNextButtons');
  var proto = Flickity.prototype;
  
  proto._createPrevNextButtons = function() {
    if ( !this.options.prevNextButtons ) {
      return;
    }
  
    this.prevButton = new PrevNextButton( -1, this );
    this.nextButton = new PrevNextButton( 1, this );
  
    this.on( 'activate', this.activatePrevNextButtons );
  };
  
  proto.activatePrevNextButtons = function() {
    this.prevButton.activate();
    this.nextButton.activate();
    this.on( 'deactivate', this.deactivatePrevNextButtons );
  };
  
  proto.deactivatePrevNextButtons = function() {
    this.prevButton.deactivate();
    this.nextButton.deactivate();
    this.off( 'deactivate', this.deactivatePrevNextButtons );
  };
  
  // --------------------------  -------------------------- //
  
  Flickity.PrevNextButton = PrevNextButton;
  
  return Flickity;
  
  }));
  
  // page dots
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/page-dots',[
        './flickity',
        'unipointer/unipointer',
        'fizzy-ui-utils/utils'
      ], function( Flickity, Unipointer, utils ) {
        return factory( window, Flickity, Unipointer, utils );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('./flickity'),
        require('unipointer'),
        require('fizzy-ui-utils')
      );
    } else {
      // browser global
      factory(
        window,
        window.Flickity,
        window.Unipointer,
        window.fizzyUIUtils
      );
    }
  
  }( window, function factory( window, Flickity, Unipointer, utils ) {
  
  // -------------------------- PageDots -------------------------- //
  
  
  
  function PageDots( parent ) {
    this.parent = parent;
    this._create();
  }
  
  PageDots.prototype = Object.create( Unipointer.prototype );
  
  PageDots.prototype._create = function() {
    // create holder element
    this.holder = document.createElement('ol');
    this.holder.className = 'flickity-page-dots';
    // create dots, array of elements
    this.dots = [];
    // events
    this.handleClick = this.onClick.bind( this );
    this.on( 'pointerDown', this.parent.childUIPointerDown.bind( this.parent ) );
  };
  
  PageDots.prototype.activate = function() {
    this.setDots();
    this.holder.addEventListener( 'click', this.handleClick );
    this.bindStartEvent( this.holder );
    // add to DOM
    this.parent.element.appendChild( this.holder );
  };
  
  PageDots.prototype.deactivate = function() {
    this.holder.removeEventListener( 'click', this.handleClick );
    this.unbindStartEvent( this.holder );
    // remove from DOM
    this.parent.element.removeChild( this.holder );
  };
  
  PageDots.prototype.setDots = function() {
    // get difference between number of slides and number of dots
    var delta = this.parent.slides.length - this.dots.length;
    if ( delta > 0 ) {
      this.addDots( delta );
    } else if ( delta < 0 ) {
      this.removeDots( -delta );
    }
  };
  
  PageDots.prototype.addDots = function( count ) {
    var fragment = document.createDocumentFragment();
    var newDots = [];
    var length = this.dots.length;
    var max = length + count;
  
    for ( var i = length; i < max; i++ ) {
      var dot = document.createElement('li');
      dot.className = 'dot';
      dot.setAttribute( 'aria-label', 'Page dot ' + ( i + 1 ) );
      fragment.appendChild( dot );
      newDots.push( dot );
    }
  
    this.holder.appendChild( fragment );
    this.dots = this.dots.concat( newDots );
  };
  
  PageDots.prototype.removeDots = function( count ) {
    // remove from this.dots collection
    var removeDots = this.dots.splice( this.dots.length - count, count );
    // remove from DOM
    removeDots.forEach( function( dot ) {
      this.holder.removeChild( dot );
    }, this );
  };
  
  PageDots.prototype.updateSelected = function() {
    // remove selected class on previous
    if ( this.selectedDot ) {
      this.selectedDot.className = 'dot';
      this.selectedDot.removeAttribute('aria-current');
    }
    // don't proceed if no dots
    if ( !this.dots.length ) {
      return;
    }
    this.selectedDot = this.dots[ this.parent.selectedIndex ];
    this.selectedDot.className = 'dot is-selected';
    this.selectedDot.setAttribute( 'aria-current', 'step' );
  };
  
  PageDots.prototype.onTap = // old method name, backwards-compatible
  PageDots.prototype.onClick = function( event ) {
    var target = event.target;
    // only care about dot clicks
    if ( target.nodeName != 'LI' ) {
      return;
    }
  
    this.parent.uiChange();
    var index = this.dots.indexOf( target );
    this.parent.select( index );
  };
  
  PageDots.prototype.destroy = function() {
    this.deactivate();
    this.allOff();
  };
  
  Flickity.PageDots = PageDots;
  
  // -------------------------- Flickity -------------------------- //
  
  utils.extend( Flickity.defaults, {
    pageDots: true
  });
  
  Flickity.createMethods.push('_createPageDots');
  
  var proto = Flickity.prototype;
  
  proto._createPageDots = function() {
    if ( !this.options.pageDots ) {
      return;
    }
    this.pageDots = new PageDots( this );
    // events
    this.on( 'activate', this.activatePageDots );
    this.on( 'select', this.updateSelectedPageDots );
    this.on( 'cellChange', this.updatePageDots );
    this.on( 'resize', this.updatePageDots );
    this.on( 'deactivate', this.deactivatePageDots );
  };
  
  proto.activatePageDots = function() {
    this.pageDots.activate();
  };
  
  proto.updateSelectedPageDots = function() {
    this.pageDots.updateSelected();
  };
  
  proto.updatePageDots = function() {
    this.pageDots.setDots();
  };
  
  proto.deactivatePageDots = function() {
    this.pageDots.deactivate();
  };
  
  // -----  ----- //
  
  Flickity.PageDots = PageDots;
  
  return Flickity;
  
  }));
  
  // player & autoPlay
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/player',[
        'ev-emitter/ev-emitter',
        'fizzy-ui-utils/utils',
        './flickity'
      ], function( EvEmitter, utils, Flickity ) {
        return factory( EvEmitter, utils, Flickity );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        require('ev-emitter'),
        require('fizzy-ui-utils'),
        require('./flickity')
      );
    } else {
      // browser global
      factory(
        window.EvEmitter,
        window.fizzyUIUtils,
        window.Flickity
      );
    }
  
  }( window, function factory( EvEmitter, utils, Flickity ) {
  
  
  
  // -------------------------- Player -------------------------- //
  
  function Player( parent ) {
    this.parent = parent;
    this.state = 'stopped';
    // visibility change event handler
    this.onVisibilityChange = this.visibilityChange.bind( this );
    this.onVisibilityPlay = this.visibilityPlay.bind( this );
  }
  
  Player.prototype = Object.create( EvEmitter.prototype );
  
  // start play
  Player.prototype.play = function() {
    if ( this.state == 'playing' ) {
      return;
    }
    // do not play if page is hidden, start playing when page is visible
    var isPageHidden = document.hidden;
    if ( isPageHidden ) {
      document.addEventListener( 'visibilitychange', this.onVisibilityPlay );
      return;
    }
  
    this.state = 'playing';
    // listen to visibility change
    document.addEventListener( 'visibilitychange', this.onVisibilityChange );
    // start ticking
    this.tick();
  };
  
  Player.prototype.tick = function() {
    // do not tick if not playing
    if ( this.state != 'playing' ) {
      return;
    }
  
    var time = this.parent.options.autoPlay;
    // default to 3 seconds
    time = typeof time == 'number' ? time : 3000;
    var _this = this;
    // HACK: reset ticks if stopped and started within interval
    this.clear();
    this.timeout = setTimeout( function() {
      _this.parent.next( true );
      _this.tick();
    }, time );
  };
  
  Player.prototype.stop = function() {
    this.state = 'stopped';
    this.clear();
    // remove visibility change event
    document.removeEventListener( 'visibilitychange', this.onVisibilityChange );
  };
  
  Player.prototype.clear = function() {
    clearTimeout( this.timeout );
  };
  
  Player.prototype.pause = function() {
    if ( this.state == 'playing' ) {
      this.state = 'paused';
      this.clear();
    }
  };
  
  Player.prototype.unpause = function() {
    // re-start play if paused
    if ( this.state == 'paused' ) {
      this.play();
    }
  };
  
  // pause if page visibility is hidden, unpause if visible
  Player.prototype.visibilityChange = function() {
    var isPageHidden = document.hidden;
    this[ isPageHidden ? 'pause' : 'unpause' ]();
  };
  
  Player.prototype.visibilityPlay = function() {
    this.play();
    document.removeEventListener( 'visibilitychange', this.onVisibilityPlay );
  };
  
  // -------------------------- Flickity -------------------------- //
  
  utils.extend( Flickity.defaults, {
    pauseAutoPlayOnHover: true
  });
  
  Flickity.createMethods.push('_createPlayer');
  var proto = Flickity.prototype;
  
  proto._createPlayer = function() {
    this.player = new Player( this );
  
    this.on( 'activate', this.activatePlayer );
    this.on( 'uiChange', this.stopPlayer );
    this.on( 'pointerDown', this.stopPlayer );
    this.on( 'deactivate', this.deactivatePlayer );
  };
  
  proto.activatePlayer = function() {
    if ( !this.options.autoPlay ) {
      return;
    }
    this.player.play();
    this.element.addEventListener( 'mouseenter', this );
  };
  
  // Player API, don't hate the ... thanks I know where the door is
  
  proto.playPlayer = function() {
    this.player.play();
  };
  
  proto.stopPlayer = function() {
    this.player.stop();
  };
  
  proto.pausePlayer = function() {
    this.player.pause();
  };
  
  proto.unpausePlayer = function() {
    this.player.unpause();
  };
  
  proto.deactivatePlayer = function() {
    this.player.stop();
    this.element.removeEventListener( 'mouseenter', this );
  };
  
  // ----- mouseenter/leave ----- //
  
  // pause auto-play on hover
  proto.onmouseenter = function() {
    if ( !this.options.pauseAutoPlayOnHover ) {
      return;
    }
    this.player.pause();
    this.element.addEventListener( 'mouseleave', this );
  };
  
  // resume auto-play on hover off
  proto.onmouseleave = function() {
    this.player.unpause();
    this.element.removeEventListener( 'mouseleave', this );
  };
  
  // -----  ----- //
  
  Flickity.Player = Player;
  
  return Flickity;
  
  }));
  
  // add, remove cell
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/add-remove-cell',[
        './flickity',
        'fizzy-ui-utils/utils'
      ], function( Flickity, utils ) {
        return factory( window, Flickity, utils );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('./flickity'),
        require('fizzy-ui-utils')
      );
    } else {
      // browser global
      factory(
        window,
        window.Flickity,
        window.fizzyUIUtils
      );
    }
  
  }( window, function factory( window, Flickity, utils ) {
  
  
  
  // append cells to a document fragment
  function getCellsFragment( cells ) {
    var fragment = document.createDocumentFragment();
    cells.forEach( function( cell ) {
      fragment.appendChild( cell.element );
    });
    return fragment;
  }
  
  // -------------------------- add/remove cell prototype -------------------------- //
  
  var proto = Flickity.prototype;
  
  /**
   * Insert, prepend, or append cells
   * @param {Element, Array, NodeList} elems
   * @param {Integer} index
   */
  proto.insert = function( elems, index ) {
    var cells = this._makeCells( elems );
    if ( !cells || !cells.length ) {
      return;
    }
    var len = this.cells.length;
    // default to append
    index = index === undefined ? len : index;
    // add cells with document fragment
    var fragment = getCellsFragment( cells );
    // append to slider
    var isAppend = index == len;
    if ( isAppend ) {
      this.slider.appendChild( fragment );
    } else {
      var insertCellElement = this.cells[ index ].element;
      this.slider.insertBefore( fragment, insertCellElement );
    }
    // add to this.cells
    if ( index === 0 ) {
      // prepend, add to start
      this.cells = cells.concat( this.cells );
    } else if ( isAppend ) {
      // append, add to end
      this.cells = this.cells.concat( cells );
    } else {
      // insert in this.cells
      var endCells = this.cells.splice( index, len - index );
      this.cells = this.cells.concat( cells ).concat( endCells );
    }
  
    this._sizeCells( cells );
    this.cellChange( index, true );
  };
  
  proto.append = function( elems ) {
    this.insert( elems, this.cells.length );
  };
  
  proto.prepend = function( elems ) {
    this.insert( elems, 0 );
  };
  
  /**
   * Remove cells
   * @param {Element, Array, NodeList} elems
   */
  proto.remove = function( elems ) {
    var cells = this.getCells( elems );
    if ( !cells || !cells.length ) {
      return;
    }
  
    var minCellIndex = this.cells.length - 1;
    // remove cells from collection & DOM
    cells.forEach( function( cell ) {
      cell.remove();
      var index = this.cells.indexOf( cell );
      minCellIndex = Math.min( index, minCellIndex );
      utils.removeFrom( this.cells, cell );
    }, this );
  
    this.cellChange( minCellIndex, true );
  };
  
  /**
   * logic to be run after a cell's size changes
   * @param {Element} elem - cell's element
   */
  proto.cellSizeChange = function( elem ) {
    var cell = this.getCell( elem );
    if ( !cell ) {
      return;
    }
    cell.getSize();
  
    var index = this.cells.indexOf( cell );
    this.cellChange( index );
  };
  
  /**
   * logic any time a cell is changed: added, removed, or size changed
   * @param {Integer} changedCellIndex - index of the changed cell, optional
   */
  proto.cellChange = function( changedCellIndex, isPositioningSlider ) {
    var prevSelectedElem = this.selectedElement;
    this._positionCells( changedCellIndex );
    this._getWrapShiftCells();
    this.setGallerySize();
    // update selectedIndex
    // try to maintain position & select previous selected element
    var cell = this.getCell( prevSelectedElem );
    if ( cell ) {
      this.selectedIndex = this.getCellSlideIndex( cell );
    }
    this.selectedIndex = Math.min( this.slides.length - 1, this.selectedIndex );
  
    this.emitEvent( 'cellChange', [ changedCellIndex ] );
    // position slider
    this.select( this.selectedIndex );
    // do not position slider after lazy load
    if ( isPositioningSlider ) {
      this.positionSliderAtSelected();
    }
  };
  
  // -----  ----- //
  
  return Flickity;
  
  }));
  
  // lazyload
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/lazyload',[
        './flickity',
        'fizzy-ui-utils/utils'
      ], function( Flickity, utils ) {
        return factory( window, Flickity, utils );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('./flickity'),
        require('fizzy-ui-utils')
      );
    } else {
      // browser global
      factory(
        window,
        window.Flickity,
        window.fizzyUIUtils
      );
    }
  
  }( window, function factory( window, Flickity, utils ) {
  'use strict';
  
  Flickity.createMethods.push('_createLazyload');
  var proto = Flickity.prototype;
  
  proto._createLazyload = function() {
    this.on( 'select', this.lazyLoad );
  };
  
  proto.lazyLoad = function() {
    var lazyLoad = this.options.lazyLoad;
    if ( !lazyLoad ) {
      return;
    }
    // get adjacent cells, use lazyLoad option for adjacent count
    var adjCount = typeof lazyLoad == 'number' ? lazyLoad : 0;
    var cellElems = this.getAdjacentCellElements( adjCount );
    // get lazy images in those cells
    var lazyImages = [];
    cellElems.forEach( function( cellElem ) {
      var lazyCellImages = getCellLazyImages( cellElem );
      lazyImages = lazyImages.concat( lazyCellImages );
    });
    // load lazy images
    lazyImages.forEach( function( img ) {
      new LazyLoader( img, this );
    }, this );
  };
  
  function getCellLazyImages( cellElem ) {
    // check if cell element is lazy image
    if ( cellElem.nodeName == 'IMG' ) {
      var lazyloadAttr = cellElem.getAttribute('data-flickity-lazyload');
      var srcAttr = cellElem.getAttribute('data-flickity-lazyload-src');
      var srcsetAttr = cellElem.getAttribute('data-flickity-lazyload-srcset');
      if ( lazyloadAttr || srcAttr || srcsetAttr ) {
        return [ cellElem ];
      }
    }
    // select lazy images in cell
    var lazySelector = 'img[data-flickity-lazyload], ' +
      'img[data-flickity-lazyload-src], img[data-flickity-lazyload-srcset]';
    var imgs = cellElem.querySelectorAll( lazySelector );
    return utils.makeArray( imgs );
  }
  
  // -------------------------- LazyLoader -------------------------- //
  
  /**
   * class to handle loading images
   */
  function LazyLoader( img, flickity ) {
    this.img = img;
    this.flickity = flickity;
    this.load();
  }
  
  LazyLoader.prototype.handleEvent = utils.handleEvent;
  
  LazyLoader.prototype.load = function() {
    this.img.addEventListener( 'load', this );
    this.img.addEventListener( 'error', this );
    // get src & srcset
    var src = this.img.getAttribute('data-flickity-lazyload') ||
      this.img.getAttribute('data-flickity-lazyload-src');
    var srcset = this.img.getAttribute('data-flickity-lazyload-srcset');
    // set src & serset
    this.img.src = src;
    if ( srcset ) {
      this.img.setAttribute( 'srcset', srcset );
    }
    // remove attr
    this.img.removeAttribute('data-flickity-lazyload');
    this.img.removeAttribute('data-flickity-lazyload-src');
    this.img.removeAttribute('data-flickity-lazyload-srcset');
  };
  
  LazyLoader.prototype.onload = function( event ) {
    this.complete( event, 'flickity-lazyloaded' );
  };
  
  LazyLoader.prototype.onerror = function( event ) {
    this.complete( event, 'flickity-lazyerror' );
  };
  
  LazyLoader.prototype.complete = function( event, className ) {
    // unbind events
    this.img.removeEventListener( 'load', this );
    this.img.removeEventListener( 'error', this );
  
    var cell = this.flickity.getParentCell( this.img );
    var cellElem = cell && cell.element;
    this.flickity.cellSizeChange( cellElem );
  
    this.img.classList.add( className );
    this.flickity.dispatchEvent( 'lazyLoad', event, cellElem );
  };
  
  // -----  ----- //
  
  Flickity.LazyLoader = LazyLoader;
  
  return Flickity;
  
  }));
  
  /*!
   * Flickity v2.2.1
   * Touch, responsive, flickable carousels
   *
   * Licensed GPLv3 for open source use
   * or Flickity Commercial License for commercial use
   *
   * https://flickity.metafizzy.co
   * Copyright 2015-2019 Metafizzy
   */
  
  ( function( window, factory ) {
    // universal module definition
    /* jshint strict: false */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity/js/index',[
        './flickity',
        './drag',
        './prev-next-button',
        './page-dots',
        './player',
        './add-remove-cell',
        './lazyload'
      ], factory );
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        require('./flickity'),
        require('./drag'),
        require('./prev-next-button'),
        require('./page-dots'),
        require('./player'),
        require('./add-remove-cell'),
        require('./lazyload')
      );
    }
  
  })( window, function factory( Flickity ) {
    /*jshint strict: false*/
    return Flickity;
  });
  
  /*!
   * Flickity asNavFor v2.0.2
   * enable asNavFor for Flickity
   */
  
  /*jshint browser: true, undef: true, unused: true, strict: true*/
  
  ( function( window, factory ) {
    // universal module definition
    /*jshint strict: false */ /*globals define, module, require */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'flickity-as-nav-for/as-nav-for',[
        'flickity/js/index',
        'fizzy-ui-utils/utils'
      ], factory );
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        require('flickity'),
        require('fizzy-ui-utils')
      );
    } else {
      // browser global
      window.Flickity = factory(
        window.Flickity,
        window.fizzyUIUtils
      );
    }
  
  }( window, function factory( Flickity, utils ) {
  
  
  
  // -------------------------- asNavFor prototype -------------------------- //
  
  // Flickity.defaults.asNavFor = null;
  
  Flickity.createMethods.push('_createAsNavFor');
  
  var proto = Flickity.prototype;
  
  proto._createAsNavFor = function() {
    this.on( 'activate', this.activateAsNavFor );
    this.on( 'deactivate', this.deactivateAsNavFor );
    this.on( 'destroy', this.destroyAsNavFor );
  
    var asNavForOption = this.options.asNavFor;
    if ( !asNavForOption ) {
      return;
    }
    // HACK do async, give time for other flickity to be initalized
    var _this = this;
    setTimeout( function initNavCompanion() {
      _this.setNavCompanion( asNavForOption );
    });
  };
  
  proto.setNavCompanion = function( elem ) {
    elem = utils.getQueryElement( elem );
    var companion = Flickity.data( elem );
    // stop if no companion or companion is self
    if ( !companion || companion == this ) {
      return;
    }
  
    this.navCompanion = companion;
    // companion select
    var _this = this;
    this.onNavCompanionSelect = function() {
      _this.navCompanionSelect();
    };
    companion.on( 'select', this.onNavCompanionSelect );
    // click
    this.on( 'staticClick', this.onNavStaticClick );
  
    this.navCompanionSelect( true );
  };
  
  proto.navCompanionSelect = function( isInstant ) {
    // wait for companion & selectedCells first. #8
    var companionCells = this.navCompanion && this.navCompanion.selectedCells;
    if ( !companionCells ) {
      return;
    }
    // select slide that matches first cell of slide
    var selectedCell = companionCells[0];
    var firstIndex = this.navCompanion.cells.indexOf( selectedCell );
    var lastIndex = firstIndex + companionCells.length - 1;
    var selectIndex = Math.floor( lerp( firstIndex, lastIndex,
      this.navCompanion.cellAlign ) );
    this.selectCell( selectIndex, false, isInstant );
    // set nav selected class
    this.removeNavSelectedElements();
    // stop if companion has more cells than this one
    if ( selectIndex >= this.cells.length ) {
      return;
    }
  
    var selectedCells = this.cells.slice( firstIndex, lastIndex + 1 );
    this.navSelectedElements = selectedCells.map( function( cell ) {
      return cell.element;
    });
    this.changeNavSelectedClass('add');
  };
  
  function lerp( a, b, t ) {
    return ( b - a ) * t + a;
  }
  
  proto.changeNavSelectedClass = function( method ) {
    this.navSelectedElements.forEach( function( navElem ) {
      navElem.classList[ method ]('is-nav-selected');
    });
  };
  
  proto.activateAsNavFor = function() {
    this.navCompanionSelect( true );
  };
  
  proto.removeNavSelectedElements = function() {
    if ( !this.navSelectedElements ) {
      return;
    }
    this.changeNavSelectedClass('remove');
    delete this.navSelectedElements;
  };
  
  proto.onNavStaticClick = function( event, pointer, cellElement, cellIndex ) {
    if ( typeof cellIndex == 'number' ) {
      this.navCompanion.selectCell( cellIndex );
    }
  };
  
  proto.deactivateAsNavFor = function() {
    this.removeNavSelectedElements();
  };
  
  proto.destroyAsNavFor = function() {
    if ( !this.navCompanion ) {
      return;
    }
    this.navCompanion.off( 'select', this.onNavCompanionSelect );
    this.off( 'staticClick', this.onNavStaticClick );
    delete this.navCompanion;
  };
  
  // -----  ----- //
  
  return Flickity;
  
  }));
  
  /*!
   * imagesLoaded v4.1.4
   * JavaScript is all like "You images are done yet or what?"
   * MIT License
   */
  
  ( function( window, factory ) { 'use strict';
    // universal module definition
  
    /*global define: false, module: false, require: false */
  
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( 'imagesloaded/imagesloaded',[
        'ev-emitter/ev-emitter'
      ], function( EvEmitter ) {
        return factory( window, EvEmitter );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('ev-emitter')
      );
    } else {
      // browser global
      window.imagesLoaded = factory(
        window,
        window.EvEmitter
      );
    }
  
  })( typeof window !== 'undefined' ? window : this,
  
  // --------------------------  factory -------------------------- //
  
  function factory( window, EvEmitter ) {
  
  
  
  var $ = window.jQuery;
  var console = window.console;
  
  // -------------------------- helpers -------------------------- //
  
  // extend objects
  function extend( a, b ) {
    for ( var prop in b ) {
      a[ prop ] = b[ prop ];
    }
    return a;
  }
  
  var arraySlice = Array.prototype.slice;
  
  // turn element or nodeList into an array
  function makeArray( obj ) {
    if ( Array.isArray( obj ) ) {
      // use object if already an array
      return obj;
    }
  
    var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
    if ( isArrayLike ) {
      // convert nodeList to array
      return arraySlice.call( obj );
    }
  
    // array of single index
    return [ obj ];
  }
  
  // -------------------------- imagesLoaded -------------------------- //
  
  /**
   * @param {Array, Element, NodeList, String} elem
   * @param {Object or Function} options - if function, use as callback
   * @param {Function} onAlways - callback function
   */
  function ImagesLoaded( elem, options, onAlways ) {
    // coerce ImagesLoaded() without new, to be new ImagesLoaded()
    if ( !( this instanceof ImagesLoaded ) ) {
      return new ImagesLoaded( elem, options, onAlways );
    }
    // use elem as selector string
    var queryElem = elem;
    if ( typeof elem == 'string' ) {
      queryElem = document.querySelectorAll( elem );
    }
    // bail if bad element
    if ( !queryElem ) {
      console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) );
      return;
    }
  
    this.elements = makeArray( queryElem );
    this.options = extend( {}, this.options );
    // shift arguments if no options set
    if ( typeof options == 'function' ) {
      onAlways = options;
    } else {
      extend( this.options, options );
    }
  
    if ( onAlways ) {
      this.on( 'always', onAlways );
    }
  
    this.getImages();
  
    if ( $ ) {
      // add jQuery Deferred object
      this.jqDeferred = new $.Deferred();
    }
  
    // HACK check async to allow time to bind listeners
    setTimeout( this.check.bind( this ) );
  }
  
  ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
  
  ImagesLoaded.prototype.options = {};
  
  ImagesLoaded.prototype.getImages = function() {
    this.images = [];
  
    // filter & find items if we have an item selector
    this.elements.forEach( this.addElementImages, this );
  };
  
  /**
   * @param {Node} element
   */
  ImagesLoaded.prototype.addElementImages = function( elem ) {
    // filter siblings
    if ( elem.nodeName == 'IMG' ) {
      this.addImage( elem );
    }
    // get background image on element
    if ( this.options.background === true ) {
      this.addElementBackgroundImages( elem );
    }
  
    // find children
    // no non-element nodes, #143
    var nodeType = elem.nodeType;
    if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
      return;
    }
    var childImgs = elem.querySelectorAll('img');
    // concat childElems to filterFound array
    for ( var i=0; i < childImgs.length; i++ ) {
      var img = childImgs[i];
      this.addImage( img );
    }
  
    // get child background images
    if ( typeof this.options.background == 'string' ) {
      var children = elem.querySelectorAll( this.options.background );
      for ( i=0; i < children.length; i++ ) {
        var child = children[i];
        this.addElementBackgroundImages( child );
      }
    }
  };
  
  var elementNodeTypes = {
    1: true,
    9: true,
    11: true
  };
  
  ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
    var style = getComputedStyle( elem );
    if ( !style ) {
      // Firefox returns null if in a hidden iframe https://bugzil.la/548397
      return;
    }
    // get url inside url("...")
    var reURL = /url\((['"])?(.*?)\1\)/gi;
    var matches = reURL.exec( style.backgroundImage );
    while ( matches !== null ) {
      var url = matches && matches[2];
      if ( url ) {
        this.addBackground( url, elem );
      }
      matches = reURL.exec( style.backgroundImage );
    }
  };
  
  /**
   * @param {Image} img
   */
  ImagesLoaded.prototype.addImage = function( img ) {
    var loadingImage = new LoadingImage( img );
    this.images.push( loadingImage );
  };
  
  ImagesLoaded.prototype.addBackground = function( url, elem ) {
    var background = new Background( url, elem );
    this.images.push( background );
  };
  
  ImagesLoaded.prototype.check = function() {
    var _this = this;
    this.progressedCount = 0;
    this.hasAnyBroken = false;
    // complete if no images
    if ( !this.images.length ) {
      this.complete();
      return;
    }
  
    function onProgress( image, elem, message ) {
      // HACK - Chrome triggers event before object properties have changed. #83
      setTimeout( function() {
        _this.progress( image, elem, message );
      });
    }
  
    this.images.forEach( function( loadingImage ) {
      loadingImage.once( 'progress', onProgress );
      loadingImage.check();
    });
  };
  
  ImagesLoaded.prototype.progress = function( image, elem, message ) {
    this.progressedCount++;
    this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
    // progress event
    this.emitEvent( 'progress', [ this, image, elem ] );
    if ( this.jqDeferred && this.jqDeferred.notify ) {
      this.jqDeferred.notify( this, image );
    }
    // check if completed
    if ( this.progressedCount == this.images.length ) {
      this.complete();
    }
  
    if ( this.options.debug && console ) {
      console.log( 'progress: ' + message, image, elem );
    }
  };
  
  ImagesLoaded.prototype.complete = function() {
    var eventName = this.hasAnyBroken ? 'fail' : 'done';
    this.isComplete = true;
    this.emitEvent( eventName, [ this ] );
    this.emitEvent( 'always', [ this ] );
    if ( this.jqDeferred ) {
      var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
      this.jqDeferred[ jqMethod ]( this );
    }
  };
  
  // --------------------------  -------------------------- //
  
  function LoadingImage( img ) {
    this.img = img;
  }
  
  LoadingImage.prototype = Object.create( EvEmitter.prototype );
  
  LoadingImage.prototype.check = function() {
    // If complete is true and browser supports natural sizes,
    // try to check for image status manually.
    var isComplete = this.getIsImageComplete();
    if ( isComplete ) {
      // report based on naturalWidth
      this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
      return;
    }
  
    // If none of the checks above matched, simulate loading on detached element.
    this.proxyImage = new Image();
    this.proxyImage.addEventListener( 'load', this );
    this.proxyImage.addEventListener( 'error', this );
    // bind to image as well for Firefox. #191
    this.img.addEventListener( 'load', this );
    this.img.addEventListener( 'error', this );
    this.proxyImage.src = this.img.src;
  };
  
  LoadingImage.prototype.getIsImageComplete = function() {
    // check for non-zero, non-undefined naturalWidth
    // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
    return this.img.complete && this.img.naturalWidth;
  };
  
  LoadingImage.prototype.confirm = function( isLoaded, message ) {
    this.isLoaded = isLoaded;
    this.emitEvent( 'progress', [ this, this.img, message ] );
  };
  
  // ----- events ----- //
  
  // trigger specified handler for event type
  LoadingImage.prototype.handleEvent = function( event ) {
    var method = 'on' + event.type;
    if ( this[ method ] ) {
      this[ method ]( event );
    }
  };
  
  LoadingImage.prototype.onload = function() {
    this.confirm( true, 'onload' );
    this.unbindEvents();
  };
  
  LoadingImage.prototype.onerror = function() {
    this.confirm( false, 'onerror' );
    this.unbindEvents();
  };
  
  LoadingImage.prototype.unbindEvents = function() {
    this.proxyImage.removeEventListener( 'load', this );
    this.proxyImage.removeEventListener( 'error', this );
    this.img.removeEventListener( 'load', this );
    this.img.removeEventListener( 'error', this );
  };
  
  // -------------------------- Background -------------------------- //
  
  function Background( url, element ) {
    this.url = url;
    this.element = element;
    this.img = new Image();
  }
  
  // inherit LoadingImage prototype
  Background.prototype = Object.create( LoadingImage.prototype );
  
  Background.prototype.check = function() {
    this.img.addEventListener( 'load', this );
    this.img.addEventListener( 'error', this );
    this.img.src = this.url;
    // check if image is already complete
    var isComplete = this.getIsImageComplete();
    if ( isComplete ) {
      this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
      this.unbindEvents();
    }
  };
  
  Background.prototype.unbindEvents = function() {
    this.img.removeEventListener( 'load', this );
    this.img.removeEventListener( 'error', this );
  };
  
  Background.prototype.confirm = function( isLoaded, message ) {
    this.isLoaded = isLoaded;
    this.emitEvent( 'progress', [ this, this.element, message ] );
  };
  
  // -------------------------- jQuery -------------------------- //
  
  ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
    jQuery = jQuery || window.jQuery;
    if ( !jQuery ) {
      return;
    }
    // set local variable
    $ = jQuery;
    // $().imagesLoaded()
    $.fn.imagesLoaded = function( options, callback ) {
      var instance = new ImagesLoaded( this, options, callback );
      return instance.jqDeferred.promise( $(this) );
    };
  };
  // try making plugin
  ImagesLoaded.makeJQueryPlugin();
  
  // --------------------------  -------------------------- //
  
  return ImagesLoaded;
  
  });
  
  /*!
   * Flickity imagesLoaded v2.0.0
   * enables imagesLoaded option for Flickity
   */
  
  /*jshint browser: true, strict: true, undef: true, unused: true */
  
  ( function( window, factory ) {
    // universal module definition
    /*jshint strict: false */ /*globals define, module, require */
    if ( typeof define == 'function' && define.amd ) {
      // AMD
      define( [
        'flickity/js/index',
        'imagesloaded/imagesloaded'
      ], function( Flickity, imagesLoaded ) {
        return factory( window, Flickity, imagesLoaded );
      });
    } else if ( typeof module == 'object' && module.exports ) {
      // CommonJS
      module.exports = factory(
        window,
        require('flickity'),
        require('imagesloaded')
      );
    } else {
      // browser global
      window.Flickity = factory(
        window,
        window.Flickity,
        window.imagesLoaded
      );
    }
  
  }( window, function factory( window, Flickity, imagesLoaded ) {
  'use strict';
  
  Flickity.createMethods.push('_createImagesLoaded');
  
  var proto = Flickity.prototype;
  
  proto._createImagesLoaded = function() {
    this.on( 'activate', this.imagesLoaded );
  };
  
  proto.imagesLoaded = function() {
    if ( !this.options.imagesLoaded ) {
      return;
    }
    var _this = this;
    function onImagesLoadedProgress( instance, image ) {
      var cell = _this.getParentCell( image.img );
      _this.cellSizeChange( cell && cell.element );
      if ( !_this.options.freeScroll ) {
        _this.positionSliderAtSelected();
      }
    }
    imagesLoaded( this.slider ).on( 'progress', onImagesLoadedProgress );
  };
  
  return Flickity;
  
  }));
  ;
!function (root, factory) {
	"function" == typeof define && define.amd ? // AMD. Register as an anonymous module unless amdModuleId is set
		define([], function () {
			return root.svg4everybody = factory();
		}) : "object" == typeof module && module.exports ? // Node. Does not work with strict CommonJS, but
			// only CommonJS-like environments that support module.exports,
			// like Node.
			module.exports = factory() : root.svg4everybody = factory();
}(this, function () {
	/*! svg4everybody v2.1.9 | github.com/jonathantneal/svg4everybody */
	function embed(parent, svg, target) {
		// if the target exists
		if (target) {
			// create a document fragment to hold the contents of the target
			var fragment = document.createDocumentFragment(), viewBox = !svg.hasAttribute("viewBox") && target.getAttribute("viewBox");
			// conditionally set the viewBox on the svg
			viewBox && svg.setAttribute("viewBox", viewBox);
			// copy the contents of the clone into the fragment
			for (// clone the target
				var clone = target.cloneNode(!0); clone.childNodes.length;) {
				fragment.appendChild(clone.firstChild);
			}
			// append the fragment into the svg
			parent.appendChild(fragment);
		}
	}
	function loadreadystatechange(xhr) {
		// listen to changes in the request
		xhr.onreadystatechange = function () {
			// if the request is ready
			if (4 === xhr.readyState) {
				// get the cached html document
				var cachedDocument = xhr._cachedDocument;
				// ensure the cached html document based on the xhr response
				cachedDocument || (cachedDocument = xhr._cachedDocument = document.implementation.createHTMLDocument(""),
					cachedDocument.body.innerHTML = xhr.responseText, xhr._cachedTarget = {}), // clear the xhr embeds list and embed each item
					xhr._embeds.splice(0).map(function (item) {
						// get the cached target
						var target = xhr._cachedTarget[item.id];
						// ensure the cached target
						target || (target = xhr._cachedTarget[item.id] = cachedDocument.getElementById(item.id)),
							// embed the target into the svg
							embed(item.parent, item.svg, target);
					});
			}
		}, // test the ready state change immediately
			xhr.onreadystatechange();
	}
	function svg4everybody(rawopts) {
		function oninterval() {
			// while the index exists in the live <use> collection
			for (// get the cached <use> index
				var index = 0; index < uses.length;) {
				// get the current <use>
				var use = uses[index], parent = use.parentNode, svg = getSVGAncestor(parent), src = use.getAttribute("xlink:href") || use.getAttribute("href");
				if (!src && opts.attributeName && (src = use.getAttribute(opts.attributeName)),
					svg && src) {
					if (polyfill) {
						if (!opts.validate || opts.validate(src, svg, use)) {
							// remove the <use> element
							parent.removeChild(use);
							// parse the src and get the url and id
							var srcSplit = src.split("#"), url = srcSplit.shift(), id = srcSplit.join("#");
							// if the link is external
							if (url.length) {
								// get the cached xhr request
								var xhr = requests[url];
								// ensure the xhr request exists
								xhr || (xhr = requests[url] = new XMLHttpRequest(), xhr.open("GET", url), xhr.send(),
									xhr._embeds = []), // add the svg and id as an item to the xhr embeds list
									xhr._embeds.push({
										parent: parent,
										svg: svg,
										id: id
									}), // prepare the xhr ready state change event
									loadreadystatechange(xhr);
							} else {
								// embed the local id into the svg
								embed(parent, svg, document.getElementById(id));
							}
						} else {
							// increase the index when the previous value was not "valid"
							++index, ++numberOfSvgUseElementsToBypass;
						}
					}
				} else {
					// increase the index when the previous value was not "valid"
					++index;
				}
			}
			// continue the interval
			(!uses.length || uses.length - numberOfSvgUseElementsToBypass > 0) && requestAnimationFrame(oninterval, 67);
		}
		var polyfill, opts = Object(rawopts), newerIEUA = /\bTrident\/[567]\b|\bMSIE (?:9|10)\.0\b/, webkitUA = /\bAppleWebKit\/(\d+)\b/, olderEdgeUA = /\bEdge\/12\.(\d+)\b/, edgeUA = /\bEdge\/.(\d+)\b/, inIframe = window.top !== window.self;
		polyfill = "polyfill" in opts ? opts.polyfill : newerIEUA.test(navigator.userAgent) || (navigator.userAgent.match(olderEdgeUA) || [])[1] < 10547 || (navigator.userAgent.match(webkitUA) || [])[1] < 537 || edgeUA.test(navigator.userAgent) && inIframe;
		// create xhr requests object
		var requests = {}, requestAnimationFrame = window.requestAnimationFrame || setTimeout, uses = document.getElementsByTagName("use"), numberOfSvgUseElementsToBypass = 0;
		// conditionally start the interval if the polyfill is active
		polyfill && oninterval();
	}
	function getSVGAncestor(node) {
		for (var svg = node; "svg" !== svg.nodeName.toLowerCase() && (svg = svg.parentNode);) { }
		return svg;
	}
	return svg4everybody;
});
/*!
Name: Reading Time
Dependencies: jQuery
Author: Michael Lynch
Author URL: http://michaelynch.com
Date Created: August 14, 2013
Date Updated: July 11, 2016
Licensed under the MIT license
*/

; (function ($) {

    var totalReadingTimeSeconds;

    $.fn.readingTime = function (options) {

        //define default parameters
        var defaults = {
            readingTimeTarget: '.eta',
            readingTimeAsNumber: false,
            wordCountTarget: null,
            wordsPerMinute: 270,
            round: true,
            lang: 'en',
            lessThanAMinuteString: '',
            prependTimeString: '',
            prependWordString: '',
            remotePath: null,
            remoteTarget: null,
            success: function () { },
            error: function () { }
        },
            plugin = this,
            el = $(this);

        //merge defaults and options
        plugin.settings = $.extend({}, defaults, options);

        //define vars
        var s = plugin.settings;

        //if no element was bound
        if (!this.length) {

            //run error callback
            s.error.call(this);

            //return so chained events can continue
            return this;
        }

        //if s.lang is set to italian
        if (s.lang == 'it') {

            var lessThanAMinute = s.lessThanAMinuteString || "Meno di un minuto";

            var minShortForm = 'min';

            //if s.lang is set to french
        } else if (s.lang == 'fr') {

            var lessThanAMinute = s.lessThanAMinuteString || "Moins d'une minute";

            var minShortForm = 'min';

            //if s.lang is set to german
        } else if (s.lang == 'de') {

            var lessThanAMinute = s.lessThanAMinuteString || "Weniger als eine Minute";

            var minShortForm = 'min';

            //if s.lang is set to spanish
        } else if (s.lang == 'es') {

            var lessThanAMinute = s.lessThanAMinuteString || "Menos de un minuto";

            var minShortForm = 'min';

            //if s.lang is set to dutch
        } else if (s.lang == 'nl') {

            var lessThanAMinute = s.lessThanAMinuteString || "Minder dan een minuut";

            var minShortForm = 'min';

            //if s.lang is set to slovak
        } else if (s.lang == 'sk') {

            var lessThanAMinute = s.lessThanAMinuteString || "Menej než minútu";

            var minShortForm = 'min';

            //if s.lang is set to czech
        } else if (s.lang == 'cz') {

            var lessThanAMinute = s.lessThanAMinuteString || "Méně než minutu";

            var minShortForm = 'min';

            //if s.lang is set to Hungarian
        } else if (s.lang == 'hu') {

            var lessThanAMinute = s.lessThanAMinuteString || "Kevesebb mint egy perc";

            var minShortForm = 'perc';

            // if s.lang is set to Russian
        } else if (s.lang == 'ru') {

            var lessThanAMinute = s.lessThanAMinuteString || "Меньше минуты";

            var minShortForm = 'мин';

            //if s.lang is set to Arabic
        } else if (s.lang == 'ar') {

            var lessThanAMinute = s.lessThanAMinuteString || "أقل من دقيقة";

            var minShortForm = 'دقيقة';

            //if s.lang is set to Danish
        } else if (s.lang == 'da') {
            var lessThanAMinute = s.lessThanAMinuteString || "Mindre end et minut";
            var minShortForm = 'min';

            //if s.lang is set to Icelandic
        } else if (s.lang == 'is') {
            var lessThanAMinute = s.lessThanAMinuteString || "Minna en eina mínútu";
            var minShortForm = 'min';

            //if s.lang is set to Norwegian
        } else if (s.lang == 'no') {
            var lessThanAMinute = s.lessThanAMinuteString || "Mindre enn ett minutt";
            var minShortForm = 'min';

            //if s.lang is set to Polish
        } else if (s.lang == 'pl') {
            var lessThanAMinute = s.lessThanAMinuteString || "Mniej niż minutę";
            var minShortForm = 'min';

            //if s.lang is set to Russian
        } else if (s.lang == 'ru') {
            var lessThanAMinute = s.lessThanAMinuteString || "Меньше минуты";
            var minShortForm = 'мой';

            //if s.lang is set to Swedish
        } else if (s.lang == 'sv') {
            var lessThanAMinute = s.lessThanAMinuteString || "Mindre än en minut";
            var minShortForm = 'min';

            //if s.lang is set to Turkish
        } else if (s.lang == 'tr') {
            var lessThanAMinute = s.lessThanAMinuteString || "Bir dakikadan az";
            var minShortForm = 'dk';

            //default s.lang is english
        } else {

            var lessThanAMinute = s.lessThanAMinuteString || 'Less than a minute';

            var minShortForm = 'min read';

        }

        var setTime = function (text) {

            if (text !== '') {

                //split text by spaces to define total words
                var totalWords = text.trim().split(/\s+/g).length;

                //define words per second based on words per minute (s.wordsPerMinute)
                var wordsPerSecond = s.wordsPerMinute / 60;

                //define total reading time in seconds
                totalReadingTimeSeconds = totalWords / wordsPerSecond;

                //define reading time in minutes
                //if s.round is set to true
                if (s.round === true) {

                    var readingTimeMinutes = Math.round(totalReadingTimeSeconds / 60);

                    //if s.round is set to false
                } else {

                    var readingTimeMinutes = Math.floor(totalReadingTimeSeconds / 60);

                }

                //define remaining reading time seconds
                var readingTimeSeconds = Math.round(totalReadingTimeSeconds - readingTimeMinutes * 60);

                //if s.round is set to true
                if (s.round === true) {

                    //if minutes are greater than 0
                    if (readingTimeMinutes > 0) {

                        //set reading time by the minute
                        $(s.readingTimeTarget).text(s.prependTimeString + readingTimeMinutes + ((!s.readingTimeAsNumber) ? ' ' + minShortForm : ''));

                    } else {

                        //set reading time as less than a minute
                        $(s.readingTimeTarget).text((!s.readingTimeAsNumber) ? s.prependTimeString + lessThanAMinute : readingTimeMinutes);

                    }

                    //if s.round is set to false
                } else {

                    //format reading time
                    var readingTime = readingTimeMinutes + ':' + readingTimeSeconds;

                    //set reading time in minutes and seconds
                    $(s.readingTimeTarget).text(s.prependTimeString + readingTime);

                }

                //if word count container isn't blank or undefined
                if (s.wordCountTarget !== '' && s.wordCountTarget !== undefined) {

                    //set word count
                    $(s.wordCountTarget).text(s.prependWordString + totalWords);

                }

                //run success callback
                s.success.call(this);

            } else {

                //run error callback
                s.error.call(this, 'The element is empty.');
            }

        };

        //for each element
        el.each(function () {

            //if s.remotePath and s.remoteTarget aren't null
            if (s.remotePath != null && s.remoteTarget != null) {

                //get contents of remote file
                $.get(s.remotePath, function (data) {

                    //set time using the remote target found in the remote file
                    setTime($('<div>').html(data).find(s.remoteTarget).text());

                });

            } else {

                //set time using the targeted element
                setTime(el.text());
            }
        });

        return totalReadingTimeSeconds;
    }


})(jQuery);;
/**
 * jQuery Flexdatalist.
 * Autocomplete input fields, with support for datalists.
 *
 * Version:
 * 2.2.4
 *
 * Depends:
 * jquery.js > 1.8.3
 *
 * Demo and Documentation:
 * http://projects.sergiodinislopes.pt/flexdatalist/
 *
 * Github:
 * https://github.com/sergiodlopes/jquery-flexdatalist/
 *
 */

jQuery.fn.flexdatalist = function (_option, _value) {
    'use strict';

    var destroy = function ($flex, clear) {
        $flex.each(function () {
            var $this = $(this),
                data = $this.data(),
                options = data.flexdatalist,
                $aliascontainer = data.aliascontainer;

            if ($aliascontainer) {
                $this.removeClass('flexdatalist-set')
                    .attr({'style': null, 'tabindex': null})
                    .val((options && options.originalValue && !clear ? options.originalValue : ''))
                    .removeData('flexdatalist')
                    .removeData('aliascontainer')
                    .off();
                $aliascontainer.remove();
            }
        });
    }

    // Callable stuff
    if (typeof _option === 'string' && _option !== 'reset') {
        if (typeof this[0].fvalue !== 'undefined') {
            var target = this[0];
            if (_option === 'destroy') {
                destroy(this, _value);
            // Get/Set value
            } else if (_option === 'value') {
                if (typeof _value === 'undefined') {
                    return target.fvalue.get();
                }
                target.fvalue.set(_value);
            // Add value
            } else if (_option === 'add') {
                if (typeof _value === 'undefined') {
                    return target.debug('Missing value to add!');
                }
                target.fvalue.add(_value);
            // Toggle value
            } else if (_option === 'toggle') {
                if (typeof _value === 'undefined') {
                    return target.debug('Missing value to toggle!');
                }
                target.fvalue.toggle(_value);
            // Remove value
            } else if (_option === 'remove') {
                if (typeof _value === 'undefined') {
                    return target.debug('Missing value to remove!');
                }
                target.fvalue.remove(_value);
            // Disabled/enabled
            } else if (_option === 'disabled') {
                if (typeof _value === 'undefined') {
                    return target.fdisabled();
                }
                target.fdisabled(_value);
            // Option(s)
            } else if (typeof _option === 'string') {
                if (typeof _value === 'undefined') {
                    return target.options.get(_option);
                }
                target.options.set(_option, _value);
            }
            return this;
        }
        _option = {_option: _value};
    }

    // Destroy if already set
    if (this.length > 0 && typeof this[0].fvalue !== 'undefined') {
        destroy(this);
    }

    var _options = $.extend({
        url: null,
        data: [],
        params: {},
        relatives: null,
        chainedRelatives: false,
        cache: true,
        cacheLifetime: 60,
        minLength: 2,
        groupBy: false,
        selectionRequired: false,
        focusFirstResult: false,
        textProperty: null,
        valueProperty: null,
        visibleProperties: [],
        iconProperty: 'thumb',
        searchIn: ['label'],
        searchContain: false,
        searchEqual: false,
        searchByWord: false,
        searchDisabled: false,
        searchDelay: 300,
        normalizeString: null,
        multiple: null,
        disabled: null,
        maxShownResults: 100,
        removeOnBackspace: true,
        noResultsText: 'No results found for "{keyword}"',
        toggleSelected: false,
        allowDuplicateValues: false,
        redoSearchOnFocus: true,
        requestType: 'get',
        requestContentType: 'x-www-form-urlencoded',
        resultsProperty: 'results',
        keywordParamName: 'keyword',
        limitOfValues: 0,
        valuesSeparator: ',',
        debug: true
    }, _option);

    return this.each(function (id) {
        var $this = $(this),
            _this = this,
            _searchTimeout = null,
            _selectedValues = [], 
            fid = 'flex' + id,
            $alias = null,
            $multiple = null;

    /**
     * Initialization
     */
        this.init = function () {
            var options = this.options.init();
            this.set.up();

            $alias
            // Focusin
            .on('focusin', function (event) {
                _this.action.redoSearchFocus(event);
                _this.action.showAllResults(event);
                if ($multiple) {
                    $multiple.addClass('focus');
                }
            })
            // Keydown
            .on('input keydown', function (event) {
                if (_this.keyNum(event) === 9) {
                    _this.results.remove();
                }
                _this.action.keypressValue(event, 188);
                _this.action.backSpaceKeyRemove(event);
            })
            // Keyup
            .on('input keyup', function (event) {
                _this.action.keypressValue(event, 13);
                _this.action.keypressSearch(event);
                _this.action.copyValue(event);
                _this.action.backSpaceKeyRemove(event);
                _this.action.showAllResults(event);
                _this.action.clearValue(event);
                _this.action.removeResults(event);
                _this.action.inputWidth(event);
            })
            // Focusout
            .on('focusout', function (event) {
                if ($multiple) {
                    $multiple.removeClass('focus');
                }
                _this.action.clearText(event);
                _this.action.clearValue(event);
            });

            window.onresize = function (event) {
                _this.position();
            };

            // Run garbage collector
            this.cache.gc();

            if (options.selectionRequired) {
                _this.fvalue.clear(true, true);
            }
            this.fvalue._load(options.originalValue, function (values, matches) {
                _this.fdisabled(options.disabled);
                $this.trigger('init:flexdatalist', [options]);
            }, true);
        }

    /**
     * Handle user actions.
     */
        this.action = {
        /**
         * Add value on comma or enter keypress.
         */
            keypressValue: function (event, keyCode) {
                var key = _this.keyNum(event),
                    val = $alias[0].value,
                    options = _this.options.get();
                if (val.length > 0
                    && key === keyCode
                    && !options.selectionRequired
                    && options.multiple) {
                        var val = $alias[0].value;
                        event.preventDefault();
                        _this.fvalue.extract(val);
                        _this.results.remove();
                }
            },
        /**
         * Check if keypress is valid.
         */
            keypressSearch: function (event) {
                var key = _this.keyNum(event),
                    keyword = $alias.val(),
                    length = keyword.length,
                    options = _this.options.get();

                clearTimeout(_searchTimeout);
                if (!key || (key !== 13 && (key < 37 || key > 40))) {
                    _searchTimeout = setTimeout(function () {
                        if ((options.minLength === 0 && length > 0) || (options.minLength > 0 && length >= options.minLength)) {
                            _this.data.load(function (data) {
                                _this.search.get(keyword, data, function (matches) {
                                    _this.results.show(matches);
                                });
                            });
                        }
                    }, options.searchDelay);
                }
            },
        /**
         * Redo search if input get's back on focus and no value selected.
         */
            redoSearchFocus: function (event) {
                var val = _this.fvalue.get(),
                    options = _this.options.get(),
                    alias = $alias.val();
                if (options.redoSearchOnFocus && ((alias.length > 0 && options.multiple) || (alias.length > 0 && val.length === 0))) {
                    this.keypressSearch(event);
                }
            },
        /**
         * Copy value from alias to target input.
         */
            copyValue: function (event) {
                if (_this.keyNum(event) !== 13) {
                    var keyword = $alias.val(),
                        val = _this.fvalue.get(true),
                        options = _this.options.get();
                    if (!options.multiple && !options.selectionRequired && keyword.length !== val.length) {
                        _this.fvalue.extract(keyword);
                    }
                }
            },
        /**
         * Remove value on backspace key (multiple input only).
         */
            backSpaceKeyRemove: function (event) {
                var options = _this.options.get();
                if (options.removeOnBackspace && options.multiple) {
                    var val = $alias.val(),
                        $remove = $alias.data('_remove');
                    if (_this.keyNum(event) === 8) {
                        if (val.length === 0) {
                            if ($remove) {
                                _this.fvalue.remove($remove);
                                $alias.data('_remove', null);
                            } else {
                                console.log('remove!');
                                $alias.data('_remove', $alias.parents('li:eq(0)').prev());
                            }
                        } else {
                            $alias.data('_remove', null);
                        }
                    }
                }
            },
        /**
         * Show all results if minLength option is 0.
         */
            showAllResults: function (event) {
                var val = $alias.val();
                val = $.trim(val);
                if (val === '' && _this.options.get('minLength') === 0) {
                    _this.data.load(function (data) {
                        _this.results.show(data);
                    });
                }
            },
        /**
         * Calculate input width by number of characters.
         */
            inputWidth: function (event) {
                var options = _this.options.get();
                if (options.multiple) {
                    var keyword = $alias.val(),
                        fontSize = parseInt($alias.css('fontSize').replace('px', '')),
                        minWidth = 40,
                        maxWidth = $this.innerWidth(),
                        width = ((keyword.length + 1) * fontSize);

                    if (width >= minWidth && width <= maxWidth) {
                        $alias[0].style.width = width + 'px';
                    }
                }
            },
        /**
         * Clear text/alias input when criteria is met.
         */
            clearText: function (event) {
                var val = _this.fvalue.get(),
                    options = _this.options.get();

                if (!options.multiple && options.selectionRequired && val.length === 0) {
                    $alias[0].value = '';
                }
            },
        /**
         * Clear value when criteria is met.
         */
            clearValue: function (event) {
                var val = _this.fvalue.get(),
                    keyword = $alias.val(),
                    options = _this.options.get();

                if (!options.multiple && options.selectionRequired && keyword.length <= options.minLength) {
                    _this.fvalue.clear();
                }
            },
        /**
         * Remove results when criteria is met.
         */
            removeResults: function (event) {
                var val = _this.fvalue.get(),
                    keyword = $alias.val(),
                    options = _this.options.get();
                if (options.minLength > 0 && keyword.length < options.minLength) {
                    _this.results.remove();
                }
            }
        }

    /**
     * Setup flex.
     */
        this.set = {
        /**
         * Prepare input replacement.
         */
            up: function () {
                $alias = this.alias();
                if (_this.options.get('multiple')) {
                    $multiple = this.multipleInput($alias);
                } else {
                    $alias.insertAfter($this);
                }
                // Respect autofocus attribute
                if ($this.attr('autofocus')) {
                    $alias.focus();
                }

                $this.data('aliascontainer', ($multiple ? $multiple : $alias)).addClass('flexdatalist flexdatalist-set').css({
                    'position': 'absolute',
                    'top': -14000,
                    'left': -14000
                }).attr('tabindex', -1);

                // update input label with alias id
                var inputId = $this.attr('id'),
                    aliasId = $alias.attr('id');
                $('label[for="' + inputId + '"]').attr('for', aliasId);

                this.chained();
            },
        /**
         * Single value input.
         */
            alias: function () {
                var aliasid = ($this.attr('id') ? $this.attr('id') + '-flexdatalist' : fid);
                var $alias = $('<input type="text">')
                    .attr({
                        'class': $this.attr('class'),
                        'name': ($this.attr('name') ? 'flexdatalist-' + $this.attr('name') : null),
                        'id': aliasid,
                        'placeholder': $this.attr('placeholder')
                    })
                    .addClass('flexdatalist-alias ' + aliasid)
                    .removeClass('flexdatalist')
                    .attr('autocomplete', 'off');
                return $alias;
            },
        /**
         * Multiple values input/list
         */
            multipleInput: function ($alias) {
                $multiple = $('<ul tabindex="1">')
                    .addClass('flexdatalist-multiple ' + fid)
                    .css({
                        'border-color': $this.css('border-left-color'),
                        'border-width': $this.css('border-left-width'),
                        'border-style': $this.css('border-left-style'),
                        'border-radius': $this.css('border-top-left-radius'),
                        'background-color': $this.css('background-color')
                    })
                    .insertAfter($this).click(function () {
                        $(this).find('input').focus();
                    });
                $('<li class="input-container">')
                    .addClass('flexdatalist-multiple-value')
                    .append($alias)
                    .appendTo($multiple);

                return $multiple;
            },
        /**
         * Chained inputs handling.
         */
            chained: function () {
                var options = _this.options.get();
                if (options.relatives && options.chainedRelatives) {
                    var toggle = function (init) {
                        options.relatives.each(function () {
                            var emptyRelative = _this.isEmpty($(this).val()),
                                empty = _this.isEmpty(_this.value);
                            // If disabling, clear all values
                            if (emptyRelative || !empty) {
                                _this.fvalue.clear();
                            }
                            _this.fdisabled(emptyRelative);
                        });
                    };
                    options.relatives.on('change', function () {
                        toggle();
                    });
                    toggle(true);
                }
            }
        }

    /**
     * Process input value(s) (where the magic happens).
     */
        this.fvalue = {
        /**
         * Get value(s).
         */
            get: function (asString) {
                var val = _this.value,
                    options = _this.options.get();
                if ((options.multiple || this.isJSON()) && !asString) {
                    return this.toObj(val);
                }
                return val;
            },
        /**
         * Set value.
         * Parse value if necessary.
         */
            set: function (val, append) {
                if (!_this.fdisabled()) {
                    if (!append) {
                        this.clear(true);
                    }
                    this._load(val);
                }
                return $this;
            },
        /**
         * Add value.
         */
            add: function (val) {
                if (_this.options.get('multiple')) {
                    this.set(val, true);
                }
                return this;
            },
        /**
         * Toggle value.
         */
            toggle: function (val) {
                if (!_this.fdisabled()) {
                    this.multiple.toggle(val);
                }
                return this;
            },
        /**
         * Remove value.
         */
            remove: function (val) {
                if (!_this.fdisabled()) {
                    val = this.toObj(val);
                    $this.trigger('before:flexdatalist.remove', [val]);
                    var result = [];
                    if ($.isArray(val)) {
                        $.each(val, function (i, value) {
                            var removed = _this.fvalue.multiple.remove(value);
                            if (removed) {
                                result.push(removed);
                            }
                        });
                    } else {
                        var _result = this.multiple.remove(val);
                        if (_result) {
                            result.push(_result);
                        }
                    }
                    $this
                        .trigger('after:flexdatalist.remove', [val, result])
                        .trigger('change:flexdatalist', [result, _this.options.get()])
                        .trigger('change');
                }
                return this;
            },
        /**
         * Load (remote?) value(s).
         */
            _load: function (values, callback, init) {
                var options = _this.options.get(),
                    valueProp = options.valueProperty,
                    _values = this.toStr(values),
                    _val = this.get(true);

                callback = (callback ? callback : $.noop);

                // If nothing changes, return
                if (_values.length == 0 && _val.length == 0) {
                    callback(values);
                    return;
                }

                values = this.toObj(values);

                if (!_this.isEmpty(values) && !_this.isEmpty(valueProp) && valueProp !== '*') {
                    if (!_this.isObject(valueProp)) {
                        valueProp = valueProp.split(',');
                    }
                    // Load data
                    _this.data.load(function (data) {
                        if (!_this.isObject(values)) {
                            values = values.split(',');
                        } else if (!$.isArray(values)) {
                            values = [values];
                        }
                        var found = [];
                        for (var idxv = 0; idxv < values.length; idxv++) {
                            var value = values[idxv];
                            for (var i = 0; i < data.length; i++) {
                                var item = data[i];
                                for (var idx = 0; idx < valueProp.length; idx++) {
                                    var prop = valueProp[idx],
                                    value = _this.isDefined(value, prop) ? value[prop] : value;
                                    if (_this.isDefined(item, prop) && value === item[prop]) {
                                        found.push(item);
                                    }
                                }
                            }
                        }
                        if (found.length > 0) {
                            _this.fvalue.extract(found, true);
                        }
                        callback(values);
                    }, values);
                    return;
                }
                callback(values);
                _this.fvalue.extract(values, init);
            },
        /**
         * Extract value and text.
         */
            extract: function (values, init) {
                var options = _this.options.get(),
                    result = [];

                if (!init) {
                    $this.trigger('before:flexdatalist.value', [values, options]);
                }

                if ($.isArray(values)) {
                    $.each(values, function (i, value) {
                        result.push(_this.fvalue._extract(value));
                    });
                } else {
                    result = _this.fvalue._extract(values);
                }

                if (!init) {
                    $this
                        .trigger('after:flexdatalist.value', [result, options])
                        .trigger('change:flexdatalist', [result, options])
                        .trigger('change');
                }
            },
        /**
         * @inherited.
         */
            _extract: function (val) {
                var txt = this.text(val),
                    value = this.value(val),
                    current = _this.value,
                    options = _this.options.get();

                if (options.multiple) {
                    // For allowDuplicateValues
                    if (!_this.isEmpty(value)) {
                        if (_this.isDup(value)) {
                            return;
                        }

                        _selectedValues.push(value);
                        this.multiple.add(value, txt);
                    }
                } else {
                    this.single(value, txt);
                }
                return {value: value, text: txt};
            },
        /**
         * Default input value.
         */
            single: function (val, txt) {
                if (txt && txt !== $alias.val()) {
                    $alias[0].value = txt;
                }
                _this.value = val;
            },
        /**
         * Input with multiple values.
         */
            multiple: {
            /**
             * Add value and item on list.
             */
                add: function (val, txt) {
                    var _multiple = this,
                        $li = this.li(val, txt),
                        options = _this.options.get();

                    // Toggle
                    $li.click(function () {
                        _multiple.toggle($(this));
                    // Remove
                    }).find('.fdl-remove').click(function () {
                        _this.fvalue.remove($(this).parent());
                    });

                    this.push(val);
                    $alias[0].value = '';
                    this.checkLimit();
                },
            /**
             * Push value to input.
             */
                push: function (val, index) {
                    var current = _this.fvalue.get();
                    val = _this.fvalue.toObj(val);
                    current.push(val);
                    val = _this.fvalue.toStr(current);
                    _this.value = val;
                },
            /**
             * Toggle value.
             */
                toggle: function (val) {
                    var options = _this.options.get();
                    if (!options.toggleSelected) {
                        return;
                    }
                    var $li = this.findLi(val);
                    if ($li) {
                        var index = $li.index(),
                            data = $li.data(),
                            action = $li.hasClass('disabled') ? 'enable' : 'disable',
                            current = _this.fvalue.get(),
                            args = [{value: data.value, text: data.text, action: action}, options];

                        $this.trigger('before:flexdatalist.toggle', args);

                        if (action === 'enable') {
                            var value = $li.data('value');
                            current.splice(index, 0, value);
                            $li.removeClass('disabled');
                        } else {
                            current.splice(index, 1);
                            $li.addClass('disabled');
                        }

                        current = _this.fvalue.toStr(current);
                        _this.value = current;

                        $this
                            .trigger('after:flexdatalist.toggle', args)
                            .trigger('change:flexdatalist', args)
                            .trigger('change');
                    }
                },
            /**
             * Remove value from input.
             */
                remove: function (val) {
                    var $li = this.findLi(val);
                    if ($li) {
                        var values = _this.fvalue.get(),
                            index = $li.index(),
                            data = $li.data(),
                            options = _this.options.get(),
                            arg = {value: data.value, text: data.text};

                        values.splice(index, 1);
                        values = _this.fvalue.toStr(values);
                        _this.value = values;
                        $li.remove();
                        _this.fvalue.multiple.checkLimit();

                        // For allowDuplicateValues
                        _selectedValues.splice(index, 1);

                        return arg;
                    }
                },
            /**
             * Remove all.
             */
                removeAll: function () {
                    var values = _this.fvalue.get(),
                        options = _this.options.get();
                    $this.trigger('before:flexdatalist.remove.all', [values, options]);
                    $multiple.find('li:not(.input-container)').remove();
                    _this.value = '';
                    _selectedValues = [];
                    $this.trigger('after:flexdatalist.remove.all', [values, options]);
                },
            /**
             * Create new item and return it.
             */
                li: function (val, txt) {
                    var $inputContainer = $multiple.find('li.input-container')
                    return $('<li>')
                        .addClass('value' + (_this.options.get('toggleSelected') ? ' toggle' : ''))
                        .append('<span class="text">' + txt + '</span>')
                        .append('<span class="fdl-remove">&times;</span>')
                        .data({
                            'text': txt,
                            'value': _this.fvalue.toObj(val)
                        })
                        .insertBefore($inputContainer);
                },
            /**
             * Create new item and return it.
             */
                checkLimit: function () {
                    var limit = _this.options.get('limitOfValues');
                    if (limit > 0) {
                        var $input = $multiple.find('li.input-container'),
                            count = _selectedValues.length;
                        (limit == count ? $input.hide() : $input.show());
                    }
                },
            /**
             * Get li item from value.
             */
                findLi: function ($li) {
                    if (!($li instanceof jQuery)) {
                        var val = $li;
                        $li = null;
                        $multiple.find('li:not(.input-container)').each(function () {
                            var $_li = $(this);
                            if ($_li.data('value') === val) {
                                $li = $_li;
                                return false;
                            }
                        });
                    } else if ($li.length === 0) {
                        $li = null;
                    }
                    return $li;
                },
            /**
             * Get li item from value.
             */
                isEmpty: function () {
                    return this.get().length > 0;
                }
            },
        /**
         * Get value that will be set on input field.
         */
            value: function (item) {
                var value = item,
                    options = _this.options.get(),
                    valueProperty = options.valueProperty;

                if (_this.isObject(item)) {
                    if (this.isJSON() || this.isMixed()) {
                        delete item.name_highlight;
                        if ($.isArray(valueProperty)) {
                            var _value = {};
                            for (var i = 0; i < valueProperty.length; i++) {
                                if (_this.isDefined(item, valueProperty[i])) {
                                    _value[valueProperty[i]] = item[valueProperty[i]];
                                }
                            }
                            value = this.toStr(_value);
                        } else {
                            value = this.toStr(item);
                        }
                    } else if (_this.isDefined(item, valueProperty)) {
                        value = item[valueProperty];
                    } else if (_this.isDefined(item, options.searchIn[0])) {
                        value = item[options.searchIn[0]];
                    } else {
                        value = null;
                    }

                }
                return value;
            },
        /**
         * Get text that will be shown to user on the alias input field.
         */
            text: function (item) {
                var text = item,
                    options = _this.options.get();
                if (_this.isObject(item)) {
                    text = item[options.searchIn[0]];
                    if (_this.isDefined(item, options.textProperty)) {
                        text = item[options.textProperty];
                    } else {
                        text = this.placeholders.replace(item, options.textProperty, text);
                    }
                }
                return $('<div>').html(text).text();
            },
        /**
         * Text placeholders processing.
         */
            placeholders: {
                replace: function (item, pattern, fallback) {
                    if (_this.isObject(item) && typeof pattern === 'string') {
                        var properties = this.parse(pattern);
                        if (!_this.isEmpty(item) && properties) {
                            $.each(properties, function (string, property) {
                                if (_this.isDefined(item, property)) {
                                    pattern = pattern.replace(string, item[property]);
                                }
                            });
                            return pattern;
                        }
                    }
                    return fallback;
                },
                parse: function (pattern) {
                    var matches = pattern.match(/\{.+?\}/g);
                    if (matches) {
                        var properties = {};
                        matches.map(function (string) {
                            properties[string] = string.slice(1, -1);
                        });
                        return properties;
                    }
                    return false;
                }
            },
        /**
         * Clear input value(s).
         */
            clear: function (alias, init) {
                var current = _this.value,
                    options = _this.options.get();

                if (options.multiple) {
                    this.multiple.removeAll();
                }
                _this.value = '';
                if (current !== '' && !init) {
                    $this.trigger('change:flexdatalist', [{value: '', text: ''}, options]).trigger('change');
                }
                if (alias) {
                    $alias[0].value = '';
                }
                _selectedValues = [];
                return this;
            },
        /**
         * Value to object.
         */
            toObj: function (val) {
                if (typeof val !== 'object') {
                    var options = _this.options.get();
                    if (_this.isEmpty(val) || !_this.isDefined(val)) {
                        val = options.multiple ? [] : (this.isJSON() ? {} : '');
                    } else if (this.isCSV()) {
                        val = val.toString().split(options.valuesSeparator);
                        val = $.map(val, function (v) {
                            return $.trim(v);
                        });
                    } else if ((this.isMixed() || this.isJSON()) && this.isJSON(val)) {
                        val = JSON.parse(val);
                    } else if (typeof val === 'number') {
                        val = val.toString();
                    }
                }
                return val;
            },
        /**
         * Is value expected to be JSON (either object or string).
         */
            toStr: function (val) {
                if (typeof val !== 'string') {
                    if (_this.isEmpty(val) || !_this.isDefined(val)) {
                        val = '';
                    } else if (typeof val === 'number') {
                        val = val.toString();
                    } else if (this.isCSV()) {
                        val = val.join(_this.options.get('valuesSeparator'));
                    } else if (this.isJSON() || this.isMixed()) {
                        val = JSON.stringify(val);
                    }
                }
                return $.trim(val);
            },
        /**
         * If argument is passed, will check if is a valid JSON object/string.
         * otherwise will check if JSON is the value expected for input
         */
            isJSON: function (str) {
                if (typeof str !== 'undefined') {
                    if (_this.isObject(str)) {
                        str = JSON.stringify(str);
                    } else if (typeof str !== 'string') {
                        return false;
                    }
                    return (str.indexOf('{') === 0 || str.indexOf('[{') === 0);
                }
                var options = _this.options.get(),
                    prop = options.valueProperty;
                return (_this.isObject(prop) || prop === '*');
            },
        /**
         * Is value expected to be JSON (either object or string).
         */
            isMixed: function () {
                var options = _this.options.get();
                return !options.selectionRequired && options.valueProperty === '*';
            },
        /**
         * Is value expected to be CSV?
         */
            isCSV: function () {
                return (!this.isJSON() && _this.options.get('multiple'));
            }
        }

    /**
     * Data.
     */
        this.data = {
        /**
         * Load data from all sources.
         */
            load: function (callback, load) {
                var __this = this,
                    data = [];
                $this.trigger('before:flexdatalist.data');
                // Remote data
                this.url(function (remote) {
                    data = data.concat(remote);
                    // Static data
                    __this.static(function (_static) {
                        data = data.concat(_static);
                        // Datalist
                        __this.datalist(function (list) {
                            data = data.concat(list);

                            $this.trigger('after:flexdatalist.data', [data]);
                            callback(data);
                        });
                    });
                }, load);
            },
        /**
         * Get static data.
         */
            static: function (callback) {
                var __this = this,
                    options = _this.options.get();
                // Remote source
                if (typeof options.data === 'string') {
                    var url = options.data,
                        cache = _this.cache.read(url, true);
                    if (cache) {
                        callback(cache);
                        return;
                    }
                    this.remote({
                        url: url,
                        success: function (data) {
                            options.data = data;
                            callback(data);
                            _this.cache.write(url, data, options.cacheLifetime, true);
                        }
                    });
                } else {
                    if (typeof options.data !== 'object') {
                        options.data = [];
                    }
                    callback(options.data);
                }
            },
        /**
         * Get datalist values.
         */
            datalist: function (callback) {
                var list = $this.attr('list'),
                    datalist = [];
                if (!_this.isEmpty(list)) {
                    $('#' + list).find('option').each(function () {
                        var $option = $(this),
                            val = $option.val(),
                            label = $option.text();
                        datalist.push({
                            label: (label.length > 0 ? label : val),
                            value: val
                        });
                    });
                }
                callback(datalist);
            },
        /**
         * Get remote data.
         */
            url: function (callback, load) {
                var __this = this,
                    keyword = $alias.val(),
                    options = _this.options.get(),
                    keywordParamName = options.keywordParamName,
                    value = _this.fvalue.get(),
                    relatives = this.relativesData();

                if (_this.isEmpty(options.url)) {
                    return callback([]);
                }

                var _opts = {};
                if (options.requestType === 'post') {
                    $.each(options, function (option, value) {
                        if (option.indexOf('_') == 0 || option == 'data') {
                            return;
                        }
                        _opts[option] = value;
                    });
                    delete _opts.relatives;
                }

                // Cache
                var cacheKey = _this.cache.keyGen({
                        relative: relatives,
                        load: load,
                        keyword: keyword,
                        contain: options.searchContain
                    }, options.url),
                    cache = _this.cache.read(cacheKey, true);
                if (cache) {
                    callback(cache);
                    return;
                }

                var data = $.extend(
                    relatives,
                    options.params,
                    {
                        load: load,
                        contain: options.searchContain,
                        selected: value,
                        original: options.originalValue,
                        options: _opts
                    }
                );
                data[keywordParamName] = keyword;

                this.remote({
                    url: options.url,
                    data: data,
                    success: function (_data) {
                        var _keyword = $alias.val();
                        // Is this really needed?
                        if (_keyword.length >= keyword.length) {
                            callback(_data);
                        }
                        _this.cache.write(cacheKey, _data, options.cacheLifetime, true);
                    }
                });
            },
        /**
         * AJAX request.
         */
            remote: function (settings) {
                var __this = this,
                    options = _this.options.get();
                // Prevent get data when pressing backspace button
                if ($this.hasClass('flexdatalist-loading')) {
                    return;
                }
                $this.addClass('flexdatalist-loading');
                if (options.requestContentType === 'json') {
                    settings.data = JSON.stringify(settings.data);
                }
                $.ajax($.extend(
                    {
                        type: options.requestType,
                        dataType: 'json',
                        contentType: 'application/' + options.requestContentType + '; charset=UTF-8',
                        complete: function () {
                            $this.removeClass('flexdatalist-loading');
                        }
                    }, settings, {
                        success: function (data) {
                            data = __this.extractRemoteData(data);
                            settings.success(data);
                        }
                    }
                ));
            },
        /**
         * Extract remote data from server response.
         */
            extractRemoteData: function (data) {
                var options = _this.options.get(),
                    _data = _this.isDefined(data, options.resultsProperty) ? data[options.resultsProperty] : data;

                if (typeof _data === 'string' && _data.indexOf('[{') === 0) {
                    _data = JSON.parse(_data);
                }
                if (_data && _data.options) {
                    _this.options.set($.extend({}, options, _data.options));
                }
                if (_this.isObject(_data)) {
                    return _data;
                }
                return [];
            },
        /**
         * Extract remote data from server response.
         */
            relativesData: function () {
                var relatives = _this.options.get('relatives'),
                    data = {};
                if (relatives) {
                    data['relatives'] = {};
                    relatives.each(function () {
                        var $_input = $(this),
                            name = $_input.attr('name')
                                .split('][').join('-')
                                .split(']').join('-')
                                .split('[').join('-')
                                .replace(/^\|+|\-+$/g, '');
                        data['relatives'][name] = $_input.val();
                    });
                }
                return data;
            }
        }

    /**
     * Search.
     */
        this.search = {
        /**
         * Search for keywords in data and return matches to given callback.
         */
            get: function (keywords, data, callback) {
                var __this = this,
                    options = _this.options.get();

                if (!options.searchDisabled) {
                    var matches = _this.cache.read(keywords);
                    if (!matches) {
                        $this.trigger('before:flexdatalist.search', [keywords, data]);
                        if (!_this.isEmpty(keywords)) {
                            matches = [];
                            var words = __this.split(keywords);
                            for (var index = 0; index < data.length; index++) {
                                var item = data[index];
                                if (_this.isDup(item)) {
                                    continue;
                                }
                                item = __this.matches(item, words);
                                if (item) {
                                    matches.push(item);
                                }
                            }
                        }
                        _this.cache.write(keywords, matches, 2);
                        $this.trigger('after:flexdatalist.search', [keywords, data, matches]);
                    }
                } else {
                    matches = data;
                }
                callback(matches);
            },
        /**
         * Match against searchable properties.
         */
            matches: function (item, keywords) {
                var hasMatches = false,
                    _item = $.extend({}, item),
                    found = [],
                    options = _this.options.get(),
                    searchIn = options.searchIn;

                if (keywords.length > 0) {
                    for (var index = 0; index < searchIn.length; index++) {
                        var searchProperty = searchIn[index];
                        if (!_this.isDefined(item, searchProperty) || !item[searchProperty]) {
                            continue;
                        }
                        var text = item[searchProperty].toString(),
                            highlight = text,
                            strings = this.split(text);
                        for (var kwindex = 0; kwindex < keywords.length; kwindex++) {
                            var keyword = keywords[kwindex];
                            if (this.find(keyword, strings)) {
                                found.push(keyword);
                                highlight = this.highlight(keyword, highlight);
                            }
                        }
                        if (highlight !== text) {
                            _item[searchProperty + '_highlight'] = this.highlight(highlight);
                        }
                    }
                }
                if (found.length === 0 || (options.searchByWord && found.length < (keywords.length - 1))) {
                    return false;
                }
                return _item;
            },
        /**
         * Wrap found keyword with span.highlight.
         */
            highlight: function (keyword, text) {
                if (text) {
                    return text.replace(
                        new RegExp(keyword, (_this.options.get('searchContain') ? "ig" : "i")),
                        '|:|$&|::|'
                    );
                }
                keyword = keyword.split('|:|').join('<span class="highlight">');
                return keyword.split('|::|').join('</span>');
            },
        /**
         * Search for keyword(s) in string.
         */
            find: function (keyword, splitted) {
                var options = _this.options.get();
                for (var index = 0; index < splitted.length; index++) {
                    var text = splitted[index];
                    text = this.normalizeString(text),
                    keyword = this.normalizeString(keyword);
                    if (options.searchEqual) {
                        return text == keyword;
                    }
                    if ((options.searchContain ? (text.indexOf(keyword) >= 0) : (text.indexOf(keyword) === 0))) {
                        return true;
                    }
                }
                return false;
            },
        /**
         * Split string by words if needed.
         */
            split: function (keywords) {
                if (typeof keywords === 'string') {
                    keywords = [$.trim(keywords)];
                }
                if (_this.options.get('searchByWord')) {
                    for (var index = 0; index < keywords.length; index++) {
                        var keyword = $.trim(keywords[index]);
                        if (keyword.indexOf(' ') > 0) {
                            var words = keyword.split(' ');
                            $.merge(keywords, words);
                        }
                    }
                }
                return keywords;
            },
        /**
         * Normalize string to a consistent one to perform the search/match.
         */
            normalizeString: function (string) {
                if (typeof string === 'string') {
                    var normalizeString = _this.options.get('normalizeString');
                    if (typeof normalizeString === 'function') {
                        string = normalizeString(string);
                    }
                    return string.toUpperCase();
                }
                return string;
            }
        }

    /**
     * Handle results.
     */
        this.results = {
        /**
         * Save key = value data in local storage (if supported)
         *
         * @param string key Data key string
         */
            show: function (results) {
                var __this = this,
                    options = _this.options.get();

                this.remove(true);

                if (!results) {
                    return;
                } else if(results.length === 0) {
                    this.empty(options.noResultsText);
                    return;
                }

                var $ul = this.container();
                if (!options.groupBy) {
                    this.items(results, $ul);
                } else {
                    results = this.group(results);
                    Object.keys(results).forEach(function (groupName, index) {
                        var items = results[groupName],
                            property = options.groupBy,
                            groupText = _this.results.highlight(items[0], property, groupName);

                        var $li = $('<li>')
                                .addClass('group')
                                .append($('<span>')
                                    .addClass('group-name')
                                    .html(groupText)
                                )
                                .append($('<span>')
                                    .addClass('group-item-count')
                                    .text(' ' + items.length)
                                )
                                .appendTo($ul);

                        __this.items(items, $ul);
                    });
                }

                var $li = $ul.find('li:not(.group)');
                $li.on('click', function (event) {
                    var item = $(this).data('item');
                    if (item) {
                        _this.fvalue.extract(item);
                        __this.remove();
                        $this.trigger('select:flexdatalist', [item, options]);
                    }
                }).hover(function () {
                    $li.removeClass('active');
                    $(this).addClass('active').trigger('active:flexdatalist.results', [$(this).data('item')]);
                }, function () {
                    $(this).removeClass('active');
                });

                if (options.focusFirstResult) {
                    $li.filter(':first').addClass('active');
                }
            },
        /**
         * Remove results container.
         */
            empty: function (text) {
                if (_this.isEmpty(text)) {
                    return;
                }
                var $container = this.container(),
                    keyword = $alias.val();

                text = text.split('{keyword}').join(keyword);
                $('<li>')
                    .addClass('item no-results')
                    .append(text)
                    .appendTo($container)
                $this.trigger('shown:flexdatalist.no-results');
            },
        /**
         * Items iteration.
         */
            items: function (items, $resultsContainer) {
                var max = _this.options.get('maxShownResults');
                $this.trigger('show:flexdatalist.results', [items]);
                for (var index = 0; index < items.length; index++) {
                    if (max > 0 && max === index) {
                        break;
                    }
                    this.item(items[index]).appendTo($resultsContainer);
                }
                $this.trigger('shown:flexdatalist.results', [items]);
            },
        /**
         * Result item creation.
         */
            item: function (item) {
                var $li = $('<li>').data('item', item).addClass('item'),
                    options = _this.options.get(),
                    visibleProperties = options.visibleProperties;

                for (var index = 0; index < visibleProperties.length; index++) {
                    var visibleProperty = visibleProperties[index];

                    if (visibleProperty.indexOf('{') > -1) {
                        var str = _this.fvalue.placeholders.replace(item, visibleProperty),
                            parsed = _this.fvalue.placeholders.parse(visibleProperty);
                        $item = $('<span>')
                            .addClass('item item-' + Object.values(parsed).join('-'))
                            .html(str + ' ').appendTo($li);
                    } else {
                        if (options.groupBy && options.groupBy === visibleProperty || !_this.isDefined(item, visibleProperty)) {
                            continue;
                        }
                        var $item = {};
                        if (visibleProperty === options.iconProperty) {
                            // Icon
                            $item = $('<img>')
                                .addClass('item item-' + visibleProperty)
                                .attr('src', item[visibleProperty]);
                        } else {
                            var propertyText = _this.results.highlight(item, visibleProperty);
                            // Other text properties
                            $item = $('<span>')
                                .addClass('item item-' + visibleProperty)
                                .html(propertyText + ' ');
                        }
                    }

                    $item.appendTo($li);
                }
                return $li;
            },
        /**
         * Results container
         */
            container: function () {
                var $target = $this;
                if ($multiple) {
                    $target = $multiple;
                }
                var $container = $('ul.flexdatalist-results');
                if ($container.length === 0) {
                    $container = $('<ul>')
                        .addClass('flexdatalist-results ')
                        .appendTo('body')
                        .attr('id', $alias.attr('id') + '-results')
                        .css({
                            'border-color': $target.css("border-left-color"),
                            'border-width': '1px',
                            'border-bottom-left-radius': $target.css("border-bottom-left-radius"),
                            'border-bottom-right-radius': $target.css("border-bottom-right-radius")
                        }).data({
                            target: ($multiple ? $multiple : $alias),
                            input: $this
                        });
                    _this.position($alias);
                }
                return $container;
            },
        /**
         * Results container
         */
            group: function (results) {
                var data = [],
                    groupProperty = _this.options.get('groupBy');
                for (var index = 0; index < results.length; index++) {
                    var _data = results[index];
                    if (_this.isDefined(_data, groupProperty)) {
                        var propertyValue = _data[groupProperty];
                        if (!_this.isDefined(data, propertyValue)) {
                            data[propertyValue] = [];
                        }
                        data[propertyValue].push(_data);
                    }
                }
                return data;
            },
        /**
         * Check if highlighted property value exists,
         * if true, return it, if not, fallback to given string
         */
            highlight: function (item, property, fallback) {
                if (_this.isDefined(item, property + '_highlight')) {
                    return item[property + '_highlight'];
                }
                return (_this.isDefined(item, property) ? item[property] : fallback);
            },
        /**
         * Remove results
         */
            remove: function (itemsOnly) {
                var selector = 'ul.flexdatalist-results';
                if (itemsOnly) {
                    selector = 'ul.flexdatalist-results li';
                }
                $this.trigger('remove:flexdatalist.results');
                $(selector).remove();
                $this.trigger('removed:flexdatalist.results');
            }
        }

    /**
    * Interface for localStorage.
    */
        this.cache = {
        /**
         * Save key = value data in local storage (if supported)
         *
         * @param string key Data key string
         * @param mixed value Value to be saved
         * @param int lifetime In Seconds
         * @return mixed
         */
            write: function (key, value, lifetime, global) {
                if (_this.cache.isSupported()) {
                    key = this.keyGen(key, undefined, global);
                    var object = {
                        value: value,
                        // Get current UNIX timestamp
                        timestamp: _this.unixtime(),
                        lifetime: (lifetime ? lifetime : false)
                    };
                    localStorage.setItem(key, JSON.stringify(object));
                }
            },
       /**
        * Read data associated with given key
        *
        * @param string key Data key string
        * @return mixed
        */
            read: function (key, global) {
                if (_this.cache.isSupported()) {
                    key = this.keyGen(key, undefined, global);
                    var data = localStorage.getItem(key);
                    if (data) {
                        var object = JSON.parse(data);
                        if (!this.expired(object)) {
                            return object.value;
                        }
                        localStorage.removeItem(key);
                    }
                }
                return null;
            },
        /**
         * Remove data associated with given key.
         *
         * @param string key Data key string
         */
            delete: function (key, global) {
                if (_this.cache.isSupported()) {
                    key = this.keyGen(key, undefined, global);
                    localStorage.removeItem(key);
                }
            },
        /**
         * Clear all data.
         */
            clear: function () {
                if (_this.cache.isSupported()) {
                    for (var key in localStorage){
                        if (key.indexOf(fid) > -1 || key.indexOf('global') > -1) {
                            localStorage.removeItem(key);
                        }
                    }
                    localStorage.clear();
                }
            },
       /**
        * Run cache garbage collector to prevent using all localStorage's
        * available space.
        */
            gc: function () {
                if (_this.cache.isSupported()) {
                    for (var key in localStorage){
                        if (key.indexOf(fid) > -1 || key.indexOf('global') > -1) {
                            var data = localStorage.getItem(key);
                            data = JSON.parse(data);
                            if (this.expired(data)) {
                                localStorage.removeItem(key);
                            }
                        }
                    }
                }
            },
       /**
        * Check if browser supports localtorage.
        *
        * @return boolean True if supports, false otherwise
        */
            isSupported: function () {
                if (_this.options.get('cache')) {
                    try {
                        return 'localStorage' in window && window['localStorage'] !== null;
                    } catch (e) {
                        return false;
                    }
                }
                return false;
            },
       /**
        * Check if cache data as expired.
        *
        * @param object object Data to check
        * @return boolean True if expired, false otherwise
        */
            expired: function (object) {
                if (object.lifetime) {
                    var diff = (_this.unixtime() - object.timestamp);
                    return object.lifetime <= diff;
                }
                return false;
            },
       /**
        * Generate cache key from object or string.
        *
        * @return string Cache key
        */
            keyGen: function (str, seed, global) {
                if (typeof str === 'object') {
                    str = JSON.stringify(str);
                }
                var i, l,
                    hval = (seed === undefined) ? 0x811c9dc5 : seed;

                for (i = 0, l = str.length; i < l; i++) {
                    hval ^= str.charCodeAt(i);
                    hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
                }
                return (global ? 'global' : fid) + ("0000000" + (hval >>> 0).toString(16)).substr(-8);
            }
        }

    /**
     * Options handler.
     */
        this.options = {
            init: function () {
                var options = $.extend({},
                    _options,
                    $this.data(),
                    {
                        multiple: (_options.multiple === null ? $this.is('[multiple]') : _options.multiple),
                        disabled: (_options.disabled === null ? $this.is('[disabled]') : _options.disabled),
                        originalValue: _this.value
                    }
                );
                this.set(options);
                return options;
            },
            get: function (option) {
                var options = $this.data('flexdatalist');
                if (!option) {
                    return options ? options : {};
                }
                return _this.isDefined(options, option) ? options[option] : null;
            },
            set: function (option, value) {
                var options = this.get();
                if (_this.isDefined(options, option) && _this.isDefined(value)) {
                    options[option] = value;
                } else if (_this.isObject(option)) {
                    options = this._normalize(option);
                }
                $this.data('flexdatalist', options);
                return $this;
            },
            _normalize: function (options) {
                options.searchIn = _this.csvToArray(options.searchIn);
                options.relatives = options.relatives && $(options.relatives).length > 0 ? $(options.relatives) : null;
                options.textProperty = options.textProperty === null ? options.searchIn[0] : options.textProperty;
                options.visibleProperties = _this.csvToArray(options.visibleProperties, options.searchIn);
                if (options.valueProperty === '*' && options.multiple && !options.selectionRequired) {
                    throw new Error('Selection must be required for multiple, JSON fields!');
                }
                return options;
            }
        }

    /**
     * Position results below parent element.
     */
        this.position = function () {
            var $results = $('ul.flexdatalist-results'),
                $target = $results.data('target');
            if ($results.length > 0) {
                // Set some required CSS properties
                $results.css({
                    'width': $target.outerWidth() + 'px',
                    'top': (($target.offset().top + $target.outerHeight())) + 'px',
                    'left': $target.offset().left + 'px'
                });
            }
        }

    /**
     * Handle disabled state.
     */
        this.fdisabled = function (disabled) {
            if (this.isDefined(disabled)) {
                $this.prop('disabled', disabled);
                $alias.prop('disabled', disabled);
                if ($multiple) {
                    $multiple.css('background-color', $this.css('background-color'));
                    var $btns = $multiple.find('li .fdl-remove'),
                        $input = $multiple.find('li.input-container');
                    if (disabled) {
                        $multiple.addClass('disabled');
                        if ($btns.length > 0) {
                            $input.hide();
                        }
                        $btns.hide();
                    } else {
                        $multiple.removeClass('disabled');
                        $input.show();
                        $btns.show();
                    }
                }
                this.options.set('disabled', disabled);
            }
            return this.options.get('disabled');
        }

    /**
     * Check for dup values.
     */
        this.isDup = function (val) {
            if (!this.options.get('allowDuplicateValues')) {
                return _selectedValues.length > 0 && _selectedValues.indexOf(this.fvalue.value(val)) > -1;
            }
            return false;
        }

    /**
     * Get key code from event.
     */
        this.keyNum = function (event) {
            return event.which || event.keyCode;
        }

    /**
     * Is variable empty.
     */
        this.isEmpty = function (value) {
            if (!_this.isDefined(value)) {
                return true;
            } else if (value === null) {
                return true;
            } else if (value === true) {
                return false;
            } else if (this.length(value) === 0) {
                return true;
            } else if ($.trim(value) === '') {
                return true;
            }
            return false;
        }

    /**
     * Is variable an object.
     */
        this.isObject = function (value) {
            return (value && typeof value === 'object');
        }

    /**
     * Get length of variable.
     */
        this.length = function (value) {
            if (this.isObject(value)) {
                return Object.keys(value).length;
            } else if (typeof value === 'number' || typeof value.length === 'number') {
                return value.toString().length;
            }
            return 0;
        }

    /**
     * Check if variable (and optionally property) is defined.
     */
        this.isDefined = function (variable, property) {
            var _variable = (typeof variable !== 'undefined');
            if (_variable && typeof property !== 'undefined') {
                return (typeof variable[property] !== 'undefined');
            }
            return _variable;
        }

    /**
     * Get unixtime stamp.
     *
     * @return boolean True if supports, false otherwise
     */
        this.unixtime = function (time) {
            var date = new Date();
            if (time) {
                date = new Date(time);
            }
            return Math.round(date.getTime() / 1000);
        }

    /**
     * To array.
     */
        this.csvToArray = function (value, _default) {
            if (value.length === 0) {
                return _default;
            }
            return typeof value === 'string' ? value.split(_this.options.get('valuesSeparator')) : value;
        }

    /**
     * Plugin warnings for debug.
     */
        this.debug = function (msg, data) {
            var options = _this.options.get();
            if (!options.debug) {
                return;
            }
            if (!data) {
                data = {};
            }
            msg = 'Flexdatalist: ' + msg;
            console.warn(msg);
            console.log($.extend({
                inputName: $this.attr('name'),
                options: options
            }, data));
            console.log('--- /flexdatalist ---');
        }

    // Go!
        this.init();
    });
}

jQuery(function ($) {
    var $document = $(document);
    // Handle results selection list keyboard shortcuts and events.
    if (!$document.data('flexdatalist')) {
        // Remove results on outside click
        $(document).mouseup(function (event) {
            var $container = $('.flexdatalist-results'),
                $target = $container.data('target');
            if ((!$target || !$target.is(':focus')) && !$container.is(event.target) && $container.has(event.target).length === 0) {
                $container.remove();
            }
        // Keyboard navigation
        }).keydown(function (event) {
            var $ul = $('.flexdatalist-results'),
                $li = $ul.find('li'),
                $active = $li.filter('.active'),
                index = $active.index(),
                length = $li.length,
                keynum = event.which || event.keyCode;

            if (length === 0) {
                return;
            }

            // on escape key, remove results
            if (keynum === 27) {
                $ul.remove();
                return;
            }

            // Enter/tab key
            if (keynum === 13) {
                event.preventDefault();
                $active.click();
            // Up/Down key
            } else if (keynum === 40 || keynum === 38) {
                event.preventDefault();
                // Down key
                if (keynum === 40) {
                    if (index < length && $active.nextAll('.item').first().length > 0) {
                        $active = $active.removeClass('active').nextAll('.item').first().addClass('active');
                    } else {
                        $active = $li.removeClass('active').filter('.item:first').addClass('active');
                    }
                // Up key
                } else if (keynum === 38) {
                    if (index > 0 && $active.prevAll('.item').first().length > 0) {
                        $active = $active.removeClass('active').prevAll('.item').first().addClass('active');
                    } else {
                        $active = $li.removeClass('active').filter('.item:last').addClass('active');
                    }
                }

                $active.trigger('active:flexdatalist.results', [$active.data('item')]);

                // Scroll to
                var position = ($active.prev().length === 0 ? $active : $active.prev()).position().top;
                $ul.animate({
                    scrollTop: position + $ul.scrollTop()
                }, 100);
            }
        }).data('flexdatalist', true);
    }

    jQuery('input.flexdatalist:not(.flexdatalist-set):not(.autodiscover-disabled)').flexdatalist();
});

(function ($) {
    var jVal = $.fn.val;
    $.fn.val = function (value) {
        var isFlex = this.length > 0 && typeof this[0].fvalue !== 'undefined';
        if (typeof value === 'undefined') {
            return isFlex ? this[0].fvalue.get(true) : jVal.call(this);
        }
        return isFlex ? this[0].fvalue.set(value) : jVal.call(this, value);
    };
})(jQuery);
;
//////
// Global variable to turn console logs off etc.
// true / false
//////
var inProduction = "true";

(function (module, $, undefined) {

    //***************//
    //***************//
    ///////////////////
    // GENERAL FUNCTIONS
    ///////////////////
    //***************//
    //***************//


    ///////////////////
    // Detect Device
    ///////////////////
    module.detectDevice = function () {
        if (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent)) {
            $('body').addClass('is-mobile');
        }
        else {
            $('body').addClass('is-desktop');
        }
    };


	///////////////////
	// IS EMAIL VALID
	///////////////////
    module.isValidEmailAddress = function (emailAddress) {
        var pattern = new RegExp(/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i);
        return pattern.test(emailAddress);
    };


	///////////////////
	// IS EMPTY OR SPACES
    ///////////////////
    module.isEmptyOrSpaces = function (str) {
        return str === null || str.match(/^ *$/) !== null;
    };


	///////////////////
	// BACK TO TOP
	///////////////////
    module.backToTop = function () {
        //Click event to scroll to top
        $(document).on('click', "[data-scroll-up]", function (e) {
            $('html, body').animate({
                scrollTop: 0
            }, 1500);
            return false;
        });
    };


	///////////////////
	// SCROLL TO
	///////////////////
    module.scrollTo = function () {
        $("[data-scroll-to]").click(function(event) {
            event.preventDefault(); 
    
            var anchor = $(this).attr('data-scroll-to');

            $('html,body').animate({ 
                scrollTop: $('#'+anchor).offset().top
            }, 1500);  
        });
    };


	///////////////////
	// SCROLL TO
	///////////////////
    module.getUrlParameter = function (name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
        var results = regex.exec(location.search);
        return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, " "));
    };




    //***************//
    //***************//
    ///////////////////
    // CUSTOM FUNCTIONS
    ///////////////////
    //***************//
    //***************//

    ///////////////////
    // BESPOKE CTA
    ///////////////////
    module.bespokeCTA = function () {

        // use state variable to keep track
        var isHandheldNavOpen = false;

        $(document).on('click', '[data-bespoke-cta-toggle]', function (e) {

            isHandheldNavOpen = !isHandheldNavOpen;
            if (isHandheldNavOpen) {
                $("[data-bespoke-cta-toggle]").attr('aria-expanded', 'true');
            } else {
                $("[data-bespoke-cta-toggle]").attr('aria-expanded', 'false');
            }
            $("[data-bespoke-cta-toggle]").toggleClass("js-active");
            $("[data-bespoke-cta-content]").toggleClass("js-active");
            $("[data-bespoke-cta-overlay]").toggleClass("js-active");

            return false;

        });

        $(document).on('click', '[data-bespoke-cta-close]', function (e) {
            $("[data-bespoke-cta-toggle]").removeClass("js-active");
            $("[data-bespoke-cta-content]").removeClass("js-active");
            $("[data-bespoke-cta-overlay]").removeClass("js-active");
        });

        $(document).on('click', '[data-bespoke-cta-overlay]', function (e) {
            $("[data-bespoke-cta-toggle]").removeClass("js-active");
            $("[data-bespoke-cta-content]").removeClass("js-active");
            $("[data-bespoke-cta-overlay]").removeClass("js-active");
        });
    };


	///////////////////
	// HOME PROODUCT BRAND SLIDER
	///////////////////
    module.homeProductBrandSlider = function () {
        var $heroSlider = $('[data-home-brand-slider]').flickity({
            wrapAround: false,
            pageDots: false,
            prevNextButtons: true,
            contain: true,
            cellAlign: 'left',
            imagesLoaded: true,
            groupCells: true,
            freeScroll: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });
    };


    ///////////////////
    // HOME SERVICE SLIDER
    ///////////////////
    module.homeServiceSlider = function () {
        var $heroSlider = $('[data-home-services-slider]').flickity({
            wrapAround: false,
            pageDots: false,
            prevNextButtons: true,
            contain: true,
            cellAlign: 'left',
            imagesLoaded: true,
            groupCells: true,
            freeScroll: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });
    };


	///////////////////
	// JOURNAL SLIDER
	///////////////////
    module.homeJournalSlider = function () {
        var $heroSlider = $('[data-journal-slider]').flickity({
            wrapAround: false,
            pageDots: false,
            prevNextButtons: true,
            contain: true,
            cellAlign: 'center',
            imagesLoaded: true,
            groupCells: true,
            freeScroll: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });
    };


	///////////////////
	// PRODUCT GALLERY SLIDER
	///////////////////
    module.productSlider = function () {
        var $heroSlider = $('[data-product-gallery]').flickity({
            wrapAround: false,
            pageDots: true,
            prevNextButtons: true,
            contain: true,
            cellAlign: 'left',
            imagesLoaded: true,
            groupCells: true,
            freeScroll: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });
    };


	///////////////////
	// HERO SLIDER
	///////////////////
    module.heroSlider = function () {
        var $heroSlider = $('[data-hero-slider]').flickity({
            wrapAround: true,
            contain: true,
            percentPosition: false,
            imagesLoaded: true,
            freeScroll: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });
    };


	///////////////////
	// IMAGE SLIDER
	///////////////////
    module.imageSlider = function () {
        var $heroSlider = $('[data-image-slider]').flickity({
            //wrapAround: true,
            contain: true,
            percentPosition: false,
            imagesLoaded: true,
            freeScroll: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });
    };


    ///////////////////
    // MENU
    ///////////////////
    module.menu = function () {
        var $heroSlider = $('[data-menu]').flickity({
            wrapAround: false,
            pageDots: false,
            watchCSS: true,
            imagesLoaded: true,
            freeScroll: true,
            cellAlign: 'left',
            contain: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });

        $(document).on('click', '[data-links-toggle]', function (e) {
            $(this).toggleClass("js-active");
            $(this).next("[data-links-wrapper]").toggleClass("js-active");
            return false;
        });
    };


	///////////////////
	// MEGA MENU
	///////////////////
    module.megaMenu = function () {

        $(document).on('click', '[data-mega-menu-toggle]', function (e) {
            $("[data-mega-menu]").toggleClass("js-active");
            $("[data-search]").removeClass('js-active');
            return false;
        });

        $(document).on('click', function (e) {
            if ($(e.target).closest("[data-mega-menu]").length === 0) {
                $("[data-mega-menu]").removeClass("js-active");
            }
        });

    };


	///////////////////
	// SEARCH
	///////////////////
    module.search = function () {
        $(document).on('click', '[data-search-toggle]', function (e) {
            $("[data-search]").toggleClass("js-active");
            $("[data-mega-menu]").removeClass('js-active');
            $('.search__input').focus();
            return false;
        });

        $(document).on('click', '[data-search-clear]', function (e) {
            $('.search__input').val("");
            $("[data-search-clear]").hide();
            return false;
        });

        $(document).on('input propertychange paste', ".search__input", function (e) {
            if ($(this).val().length > 0) {
                $("[data-search-clear]").show();
            }
            else {
                $("[data-search-clear]").hide();
            }
        });

        $(document).on('click', function (e) {
            if ($(e.target).closest("[data-search]").length === 0) {
                $("[data-search]").removeClass("js-active");
            }
        });

        //$('.search__input').flexdatalist({
        //    requestType: 'get',
        //    minLength: 1,
        //    url: '/handlers/AutoCompleteSearch.ashx',
        //    params: { "type": "product" }, 
        //    cache: false,
        //    maxShownResults: 5, 
        //    visibleProperties: ["Product_Name"],
        //    searchDisabled: true, 
        //    valueProperty: 'Product_Name',
        //    textProperty: 'Product_Name',
        //    noResultsText: 'Press <strong>Enter</strong> to search for "{keyword}"'
        //});


        if ($(".search__input").length) {
            $('.search__input').flexdatalist({
                requestType: 'get',
                minLength: 1,
                url: '/handlers/AutoCompleteSearch.ashx',
                params: { "type": "product" },
                cache: 0,
                searchDisabled: 1,
                visibleProperties: ["Product_Name"],
                textProperty: 'Product_Name',
                valueProperty: 'Product_Name',
                noResultsText: 'Press <strong>Enter</strong> to search for "{keyword}"'
            }).on("shown:flexdatalist.no-results", function (event, results) {
                $(".flexdatalist-results").appendTo(".search__wrapper");
            }).on("show:flexdatalist.results", function (event, results) {
                $(".flexdatalist-results").appendTo(".search__wrapper");
            });

            $(".search__input").on("select:flexdatalist.data", function (event, results) {
                document.getElementById("btn_Search").click();
            });
        }
    };


	///////////////////
	// QUANTITIY INPUT
	///////////////////
    module.quantitiyInput = function () {

        $(document).on('click', '[data-quantity-decrease]', function (e) {
            var iQty = 0;
            var container = $(this).parents("[data-quantity-wrapper]");
            var currentValue = container.find("[data-quantity-input]").val();

            if (currentValue >= 1) {
                iQty = Number(currentValue) - 1;
                container.children("[data-quantity-input]").val(iQty);
            }
            return false;
        });

        $(document).on('click', '[data-quantity-increase]', function (e) {
            var iQty = 0;
            var container = $(this).parents("[data-quantity-wrapper]");
            var currentValue = container.find("[data-quantity-input]").val();

            iQty = Number(currentValue) + 1;
            container.children("[data-quantity-input]").val(iQty);
            return false;
        });
    };


	///////////////////
	// CATEGORY SLIDER
	///////////////////
    module.categorySlider = function () {
        var $heroSlider = $('[data-category-slider]').flickity({
            wrapAround: false,
            pageDots: false,
            prevNextButtons: true,
            imagesLoaded: true,
            contain: true,
            cellAlign: 'left',
            groupCells: true,
            arrowShape: 'M62.5 20.8c1.1 0 2.1.4 2.9 1.2 1.6 1.6 1.6 4.3 0 5.9L43.4 50l22.1 22.1c1.6 1.6 1.6 4.3 0 5.9-1.6 1.6-4.3 1.6-5.9 0l-25-25c-1.6-1.6-1.6-4.3 0-5.9l25-25c.8-.9 1.8-1.3 2.9-1.3z'
        });
    };


    ///////////////////
    // NAVIGATION
    ///////////////////
    module.navigation = function () {

        // use state variable to keep track
        var isHandheldNavOpen = false;

        $(document).on('click', '[data-navigation-toggle]', function (e) {

            isHandheldNavOpen = !isHandheldNavOpen;
            if (isHandheldNavOpen) {
                $("[data-navigation-toggle]").attr('aria-expanded', 'true');
            } else {
                $("[data-navigation-toggle]").attr('aria-expanded', 'false');
            }

            $("[data-navigation-toggle] svg").toggleClass("js-active");
            $("[data-handheld-navigation]").toggleClass("js-active");

            $("body").toggleClass("no-scroll");

            return false;

        });

        $(document).on('click', '[data-handheld-modal-toggle]', function (e) {

            isHandheldNavOpen = !isHandheldNavOpen;
            if (isHandheldNavOpen) {
                $("[data-navigation-toggle]").attr('aria-expanded', 'true');
            } else {
                $("[data-navigation-toggle]").attr('aria-expanded', 'false');
            }

            $("[data-navigation-toggle] svg").toggleClass("js-active");
            $("[data-handheld-navigation]").toggleClass("js-active");
            $("body").toggleClass("no-scroll");
            return false;

        });
    };


    ///////////////////
    // MAP
    ///////////////////
    module.map = function () {

        function initialize() {
            $('[data-map]').each(function (index, Element) {
                var latitude = $(this).data("latitude");
                var longitude = $(this).data("longitude");

                //console.log(longitude);
                //console.log(latitude);

                var imageType = '.png';

                if (!!navigator.userAgent.match(/Trident.*rv:11./) === true) {
                    imageType = '.png';
                }
                var markerPin = {
                    url: '/images/icons/map-marker' + imageType,
                    // This marker is 20 pixels wide by 32 pixels high.
                    size: new google.maps.Size(28, 39),
                    // The origin for this image is (0, 0).
                    origin: new google.maps.Point(0, 0),
                    // The anchor for this image is the base of the flagpole at (0, 32).
                    anchor: new google.maps.Point(14, 19)
                };
                var mapStyle = [
                    {
                        "featureType": "administrative",
                        "elementType": "all",
                        "stylers": [
                            {
                                "visibility": "off"
                            }
                        ]
                    },
                    {
                        "featureType": "administrative",
                        "elementType": "labels.text.fill",
                        "stylers": [
                            {
                                "color": "#444444"
                            }
                        ]
                    },
                    {
                        "featureType": "landscape",
                        "elementType": "all",
                        "stylers": [
                            {
                                "color": "#f2f2f2"
                            }
                        ]
                    },
                    {
                        "featureType": "poi",
                        "elementType": "geometry.fill",
                        "stylers": [
                            {
                                "saturation": "-100"
                            },
                            {
                                "lightness": "57"
                            }
                        ]
                    },
                    {
                        "featureType": "poi",
                        "elementType": "geometry.stroke",
                        "stylers": [
                            {
                                "lightness": "1"
                            }
                        ]
                    },
                    {
                        "featureType": "poi",
                        "elementType": "labels",
                        "stylers": [
                            {
                                "visibility": "off"
                            }
                        ]
                    },
                    {
                        "featureType": "road",
                        "elementType": "all",
                        "stylers": [
                            {
                                "saturation": -100
                            },
                            {
                                "lightness": 45
                            }
                        ]
                    },
                    {
                        "featureType": "road.highway",
                        "elementType": "all",
                        "stylers": [
                            {
                                "visibility": "simplified"
                            }
                        ]
                    },
                    {
                        "featureType": "road.arterial",
                        "elementType": "labels.icon",
                        "stylers": [
                            {
                                "visibility": "off"
                            }
                        ]
                    },
                    {
                        "featureType": "transit",
                        "elementType": "all",
                        "stylers": [
                            {
                                "visibility": "off"
                            }
                        ]
                    },
                    {
                        "featureType": "transit",
                        "elementType": "geometry",
                        "stylers": [
                            {
                                "visibility": "on"
                            }
                        ]
                    },
                    {
                        "featureType": "transit",
                        "elementType": "labels",
                        "stylers": [
                            {
                                "visibility": "off"
                            }
                        ]
                    },
                    {
                        "featureType": "transit",
                        "elementType": "labels.text",
                        "stylers": [
                            {
                                "visibility": "off"
                            }
                        ]
                    },
                    {
                        "featureType": "transit",
                        "elementType": "labels.text.fill",
                        "stylers": [
                            {
                                "visibility": "off"
                            },
                            {
                                "color": "#484848"
                            }
                        ]
                    },
                    {
                        "featureType": "transit",
                        "elementType": "labels.text.stroke",
                        "stylers": [
                            {
                                "visibility": "off"
                            }
                        ]
                    },
                    {
                        "featureType": "transit",
                        "elementType": "labels.icon",
                        "stylers": [
                            {
                                "visibility": "on"
                            }
                        ]
                    },
                    {
                        "featureType": "transit.station.bus",
                        "elementType": "all",
                        "stylers": [
                            {
                                "visibility": "on"
                            }
                        ]
                    },
                    {
                        "featureType": "transit.station.bus",
                        "elementType": "labels.text.fill",
                        "stylers": [
                            {
                                "saturation": "0"
                            },
                            {
                                "lightness": "0"
                            },
                            {
                                "gamma": "1.00"
                            },
                            {
                                "weight": "1"
                            }
                        ]
                    },
                    {
                        "featureType": "transit.station.bus",
                        "elementType": "labels.icon",
                        "stylers": [
                            {
                                "saturation": "-100"
                            },
                            {
                                "weight": "1"
                            },
                            {
                                "lightness": "0"
                            }
                        ]
                    },
                    {
                        "featureType": "transit.station.rail",
                        "elementType": "all",
                        "stylers": [
                            {
                                "visibility": "on"
                            }
                        ]
                    },
                    {
                        "featureType": "transit.station.rail",
                        "elementType": "labels.text.fill",
                        "stylers": [
                            {
                                "gamma": "1"
                            },
                            {
                                "lightness": "40"
                            }
                        ]
                    },
                    {
                        "featureType": "transit.station.rail",
                        "elementType": "labels.icon",
                        "stylers": [
                            {
                                "saturation": "-100"
                            },
                            {
                                "lightness": "30"
                            }
                        ]
                    },
                    {
                        "featureType": "water",
                        "elementType": "all",
                        "stylers": [
                            {
                                "color": "#d2d2d2"
                            },
                            {
                                "visibility": "on"
                            }
                        ]
                    }
                ];
                
                var myLatLng = {lat: latitude, lng: longitude};
                //var myLatLng = {lat: 51.8868634, lng: -2.0756753};

                var map = new google.maps.Map(this, {
                    zoom: 16,
                    center: myLatLng,
                    mapTypeId: google.maps.MapTypeId.ROADMAP,
                    styles: mapStyle,
                    mapTypeControl: false,
                    scaleControl: false,
                    streetViewControl: false,
                    fullscreenControl: false,
                    zoomControl: true,
                    zoomControlOptions: {
                        position: google.maps.ControlPosition.TOP_RIGHT
                    },
                });
        
                marker = new google.maps.Marker({
                    map: map,
                    position: myLatLng,
                    icon: markerPin
                });
            });
        }

        if ($("[data-map]").length) {
            google.maps.event.addDomListener(window, 'load', initialize); 
        }


    };


    ///////////////////
    // READING TIME ARTICLE
    ///////////////////
    module.readingTimeArticle = function () {

        if ($(".news-article__content").length) {
            $('.news-article__content').readingTime({
                wordsPerMinute: 200,
                readingTimeTarget: ".news-article [data-time-to-read]"
            });
        }

    };


    ///////////////////
    // READING TIME
    ///////////////////
    module.readingTime = function () {

        if ($(".news-card").length) {
            $('.news-card').each(function () {
                $(this).readingTime({
                    wordsPerMinute: 200,
                    readingTimeTarget: $(this).find('[data-time-to-read]'),
                    remotePath: $(this).attr('data-file'),
                    remoteTarget: ".news-article__content",
                    success: function (data) {
                    },
                    error: function (data) {
                        $('.news-card__read-time').remove();
                    }
                });

            });
        }

    };


    ///////////////////
	// NEWSLETTER
    ///////////////////
    module.newsletterForm = function () {
        $("[data-newsletter-submit]").click(function (e) {
            e.preventDefault();

            var bValid = true;

            if ($("[data-newsletter-email]").val()) {
                if (!module.isValidEmailAddress($("[data-newsletter-email]").val())) {
                    bValid = false;
                    $("[data-newsletter-email]").parent(".form__group").removeClass("form__group--error");
                    $("[data-newsletter-email]").parent(".form__group").addClass("form__group--invalid");
                }
                else {
                    $("[data-newsletter-email]").parent(".form__group").removeClass("form__group--invalid");
                    $("[data-newsletter-email]").parent(".form__group").removeClass("form__group--error");
                }
            }
            else {
                bValid = false;
                $("[data-newsletter-email]").parent(".form__group").removeClass("form__group--invalid");
                $("[data-newsletter-email]").parent(".form__group").addClass("form__group--error");
            }

            if (bValid) {
                var MailChimpAPI = new Object();
                MailChimpAPI.EmailAddress = $("[data-newsletter-email]").val();

                var DTO = { 'MailChimpAPI': MailChimpAPI };
                $.ajax({
                    type: "POST",
                    url: "/services/mc.asmx/SignUp",
                    data: JSON.stringify(DTO),
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    success: function (msg) {
                        switch (msg.d) {
                            case "invalid setup":
                                module.notification("error", 6000, "We're sorry but something has gone wrong, please try again later.", "newsletterInvalidSetup");
                                break;

                            case "invalid email":
                                module.notification("warning", 6000, "Please enter a valid email address.", "newsletterEmailInvalid");
                                break;

                            case "error":
                                module.notification("warning", 6000, "Something seems to have gone wrong! Please try again later.", "newsletterError");
                                break;

                            case "okay":
                                module.notification("success", 6000, "You have been added to our newsletter.", "newsletterSuccess");
                                break;
                        }
                    },
                    error: function (request, status, error) {
                        module.notification("error", 6000, "Something seems to have gone wrong! Please try again later.", "newsletterError");
                    }
                });
            }
            else {
                module.notification("error", 6000, "Something seems to have gone wrong! Please try again later.", "newsletterError");
            }

            return false;
        });

        $("[data-newsletter-email]").on("keypress", function (e) {
            var keycode = (event.keyCode ? event.keyCode : event.which);
            if (keycode === 13) {
                e.preventDefault();
                $("[data-newsletter-submit]").click();
                return false;
            }
        });
    };


    ///////////////////
    // STOCKIST MAP
    ///////////////////
    module.stockistMap = function () {

        function initialize() {

            markers = [];

            var infowindow = new google.maps.InfoWindow();
            var bounds = new google.maps.LatLngBounds();
            var mapStyle = [
                {
                    "featureType": "all",
                    "elementType": "labels.text.fill",
                    "stylers": [
                        {
                            "saturation": 36
                        },
                        {
                            "color": "#333333"
                        },
                        {
                            "lightness": 40
                        }
                    ]
                },
                {
                    "featureType": "all",
                    "elementType": "labels.text.stroke",
                    "stylers": [
                        {
                            "visibility": "on"
                        },
                        {
                            "color": "#ffffff"
                        },
                        {
                            "lightness": 16
                        }
                    ]
                },
                {
                    "featureType": "all",
                    "elementType": "labels.icon",
                    "stylers": [
                        {
                            "visibility": "off"
                        }
                    ]
                },
                {
                    "featureType": "administrative",
                    "elementType": "geometry.fill",
                    "stylers": [
                        {
                            "color": "#fefefe"
                        },
                        {
                            "lightness": 20
                        }
                    ]
                },
                {
                    "featureType": "administrative",
                    "elementType": "geometry.stroke",
                    "stylers": [
                        {
                            "color": "#fefefe"
                        },
                        {
                            "lightness": 17
                        },
                        {
                            "weight": 1.2
                        }
                    ]
                },
                {
                    "featureType": "landscape",
                    "elementType": "geometry",
                    "stylers": [
                        {
                            "color": "#f5f5f5"
                        },
                        {
                            "lightness": 20
                        }
                    ]
                },
                {
                    "featureType": "poi",
                    "elementType": "geometry",
                    "stylers": [
                        {
                            "color": "#f5f5f5"
                        },
                        {
                            "lightness": 21
                        }
                    ]
                },
                {
                    "featureType": "poi.park",
                    "elementType": "geometry",
                    "stylers": [
                        {
                            "color": "#dedede"
                        },
                        {
                            "lightness": 21
                        }
                    ]
                },
                {
                    "featureType": "road.highway",
                    "elementType": "geometry.fill",
                    "stylers": [
                        {
                            "color": "#ffffff"
                        },
                        {
                            "lightness": 17
                        }
                    ]
                },
                {
                    "featureType": "road.highway",
                    "elementType": "geometry.stroke",
                    "stylers": [
                        {
                            "color": "#ffffff"
                        },
                        {
                            "lightness": 29
                        },
                        {
                            "weight": 0.2
                        }
                    ]
                },
                {
                    "featureType": "road.arterial",
                    "elementType": "geometry",
                    "stylers": [
                        {
                            "color": "#ffffff"
                        },
                        {
                            "lightness": 18
                        }
                    ]
                },
                {
                    "featureType": "road.local",
                    "elementType": "geometry",
                    "stylers": [
                        {
                            "color": "#ffffff"
                        },
                        {
                            "lightness": 16
                        }
                    ]
                },
                {
                    "featureType": "transit",
                    "elementType": "geometry",
                    "stylers": [
                        {
                            "color": "#f2f2f2"
                        },
                        {
                            "lightness": 19
                        }
                    ]
                },
                {
                    "featureType": "water",
                    "elementType": "geometry",
                    "stylers": [
                        {
                            "color": "#e9e9e9"
                        },
                        {
                            "lightness": 17
                        }
                    ]
                }
            ];
            var marker, i;
            var propertyCount = 0;


            window.map = new google.maps.Map(document.getElementById('stockist-map'), {
                mapTypeId: google.maps.MapTypeId.ROADMAP,
                styles: mapStyle
            });


            for (i = 0; i < locations.length; i++) {

                var imageType = '.svg';

                if (!!navigator.userAgent.match(/Trident.*rv:11./) === true) {
                    imageType = '.png';
                }
                var markerPin = '/images/icons/';

                var locationTypes = locations[i][6].split("|");

                if (locationTypes[0] === "Closest Outlet") {
                    markerPin += 'map-marker--green' + imageType;
                }
                else if (locationTypes[0] === "Distributors") {
                    markerPin += 'map-marker--red' + imageType;
                }
                else {
                    markerPin += 'map-marker' + imageType;
                }

                position = new google.maps.LatLng(locations[i][1], locations[i][2]);

                marker = new google.maps.Marker({
                    position: position,
                    map: map,
                    icon: markerPin
                });
                
                propertyCount++;

                bounds.extend(position);
                
                markers.push(marker);

                google.maps.event.addListener(marker, 'click', (function (marker, i) {
                    return function () {
                        var locationBadges = "";
                        locations[i][6].split("|").forEach(strLocation => {
                            switch (strLocation) {
                                case "PV Logic":
                                    locationBadges += "<span class='badge badge__pv-logic badge--rounded'>" + strLocation + "</span>";
                                    break;
                                case "SolarMate":
                                    locationBadges += "<span class='badge badge__solarmate badge--rounded'>" + strLocation + "</span>";
                                    break;
                                case "Hubi":
                                    locationBadges += "<span class='badge badge__hubi badge--rounded'>" + strLocation + "</span>";
                                    break;
                                case "Lifos":
                                    locationBadges += "<span class='badge badge__lifos badge--rounded'>" + strLocation + "</span>";
                                    break;
                                default:
                                    locationBadges += "<span class='badge badge__light-grey badge--rounded'>" + strLocation + "</span>";
                            }
                            
                        });

                        infowindow.setContent("" +
                            "<div class='stockist__card stockist__card--map'>" +
                            "<div class='stockist__card-content'>" +
                            "<h3 class='heading-3'>" + locations[i][0] + "</h2>" +
                            "<address class='stockist__card-address'>" + locations[i][3] + "</address>" +
                            "<p class='stockist__card-telephone'>" + locations[i][4] + "</p>" +
                            "<a href='" + locations[i][5] + "/' target='_blank' class='stockist__card-website'>" + locations[i][5] + "</a>" +
                            "</div>" +
                            "<div class='stockist__card-footer'>" +
                            locationBadges +
                            "</div>" +
                            "</div>" +
                            "");
                        infowindow.open(map, marker);
                    };
                })(marker, i));

            }

            if (propertyCount > 1) {
                //map.setCenter(bounds.getCenter());

                map.fitBounds(bounds);

                var clusterStyles = [{
                    textColor: "white",
                    url: "/images/icons/map-marker--cluster.svg",
                    height: 30,
                    width: 30,
                    //anchorIcon: [20, 140]
                }]


                var markerCluster = new MarkerClusterer(map, markers, {
                    styles: clusterStyles
                });    
            }
            else if (propertyCount === 1) {
                map.setCenter(bounds.getCenter());
                //map.fitBounds(bounds);

                map.setZoom(12);
            }
            else {
                var LatLng = new google.maps.LatLng(51.862097, -2.544233);

                map.setCenter(LatLng);
                map.setZoom(6);
            }
        }

        if ($("#stockist-map").length) {
            google.maps.event.addDomListener(window, 'load', initialize);
        }

    };


    ///////////////////
    // Accordions
    ///////////////////
    module.accordion = function () {

        var btnTrigger = '.accordion__header';
        var objContent = '.accordion__content';
        var boolShowFirst = true;

        //Show the first element's contents if true
        if (boolShowFirst === true) {
            $(objContent).eq(0).show();
            $(btnTrigger).eq(0).addClass('is-active');
        }

        ///////
        //Events
        ///////

        //Set up a click function
        $(document).on('click', btnTrigger, function (e) {
            //If the trigger has the current class, 
            if ($(this).hasClass('is-active')) {
                //Slide up it's content panel and remove current class
                $(this).next().slideUp('fast');
                $(this).removeClass('is-active');
            }
            else {
                //Loop the content panels
                $(objContent).each(function () {
                    //Slide them up
                    $(this).slideUp('fast');
                });
                //Loop the triggers
                $(btnTrigger).each(function () {
                    //Remove the current class
                    $(this).removeClass('is-active');

                });

                //Slide down the content panel we need.
                $(this).next().slideDown('fast');
                //Add the current class to the trigger
                $(this).addClass('is-active');
            }

            module.scrollTable();

            //e.preventDefault();
            e.stopImmediatePropagation();
            return false;
        });

        // setup specific buttons for specific panels
        $("#btn__goToDetails").on("click", function (e) {
            if ($("#product-information").hasClass('is-active')) {
                // already active, don't need to do anything
            }
            else {
                //Loop the content panels
                $(objContent).each(function () {
                    //Slide them up
                    $(this).slideUp('fast');
                });
                //Loop the triggers
                $(btnTrigger).each(function () {
                    //Remove the current class
                    $(this).removeClass('is-active');
                });

                //Slide down the content panel we need.
                $("#product-information").next().slideDown('fast');
                //Add the current class to the trigger
                $("#product-information").addClass('is-active');
            }

            $('html, body').animate({
                scrollTop: $("#product-information").offset().top - 100
            }, 'slow');

            e.preventDefault();
        });

        $("#btn__goToReviews").on("click", function (e) {
            if ($("#reviews").hasClass('is-active')) {
                // already active, don't need to do anything
            }
            else {
                //Loop the content panels
                $(objContent).each(function () {
                    //Slide them up
                    $(this).slideUp('fast');
                });
                //Loop the triggers
                $(btnTrigger).each(function () {
                    //Remove the current class
                    $(this).removeClass('is-active');
                });

                //Slide down the content panel we need.
                $("#reviews").next().slideDown('fast');
                //Add the current class to the trigger
                $("#reviews").addClass('is-active');
            }

            $('html, body').animate({
                scrollTop: $("#reviews").offset().top - 100
            }, 'slow');

            e.preventDefault();
        });
    }

    module.UpgradeASPNETValidation = function () {
        if (typeof (Page_ClientValidate) !== "undefined") {
            AspValidatorUpdateDisplay = ValidatorUpdateDisplay;
            ValidatorUpdateDisplay = NicerValidatorUpdateDisplay;
            AspValidatorValidate = ValidatorValidate;
            ValidatorValidate = NicerValidatorValidate;

            // Remove the error class on each control group before validating
            // Store a reference to the ClientValidate function
            var origValidate = Page_ClientValidate;
            // Override with our custom version
            Page_ClientValidate = function (validationGroup) {
                // Call the original function
                origValidate(validationGroup);
            };
        }

        function NicerValidatorUpdateDisplay(val) {
            // Do the default asp.net display of validation errors (remove if you want)
            AspValidatorUpdateDisplay(val);

            // Add our custom display of validation errors
            // IF we should be paying any attention to this validator at all
            if ((typeof (val.enabled) === "undefined" || val.enabled !== false) && IsValidationGroupMatch(val, AspValidatorValidating)) {
                if (!val.isvalid) {
                    // Set css class for invalid controls
                    if (!$('#' + val.controltovalidate).parent().hasClass('form__item--error')) {
                        $('#' + val.controltovalidate).parent().addClass('form__item--error');
                    }
                } else {

                    var safeToRemove = true;

                    $('#' + val.controltovalidate).parent().children('.form__required--error').each(function () {
                        if (!this.isvalid) { safeToRemove = false; }
                    });

                    if (safeToRemove) {
                        $('#' + val.controltovalidate).parent().removeClass('form__item--error');
                    }

                    //var test = $('#' + val.controltovalidate).parent().children('.form__required--error');

                    //if ($('#' + val.controltovalidate).parent().children('.form__required--error').is(':visible')) {
                    //    // still other errors, don't remove
                    //}
                    //else {
                    //    // should be safe to remove
                    //    $('#' + val.controltovalidate).parent().removeClass('form__item--error');
                    //}
                }
            }
        }

        function NicerValidatorValidate(val, validationGroup, event) {
            AspValidatorValidating = validationGroup;
            AspValidatorValidate(val, validationGroup, event);
        }
    };


    ///////////////////
    // Notification
    ///////////////////
    module.notification = function (Type, Duration, Text, ID) {
        // Notification types - error, success, notice, warning 
        // module.notification({{NOTIFICATION TYPE}}, {{NOTIFICATION DURATION}}, {{NOTIFICATION TEXT (CAN INCLUDE HTML)}}, {{NOTIFICATION ID}});

        // Default Options
        var options = $.extend({
            notificationWrapperClass: "notification__wrapper",
            notificationItemClass: "notification",
            closeButtonClass: "notification__close"
        }, options);

        // Notification Template
        var notificationTemplate = "<div id='" + options.notificationItemClass + "--" + ID + "' class='" + options.notificationItemClass + " " + options.notificationItemClass + "--" + Type + "'>" + Text + "<div class='" + options.closeButtonClass + "'></div></div>";


        // Added notification
        $("." + options.notificationWrapperClass).prepend(notificationTemplate);

        // Set timeout to close notification
        setTimeout(function () {
            $("#" + options.notificationItemClass + "--" + ID).fadeOut();
        }, Duration);

        // Close notification on click
        $("." + options.closeButtonClass).on('click', function (e) {
            $(this).parent("." + options.notificationItemClass + "").fadeOut();
        });

    };


    /////////////////////
    //// Modal Window
    /////////////////////
    module.modal = function () {

        settings = {
            //Model Popup
            objModalPopupBtn: ".modal__trigger",
            objModalCloseBtn: ".modal, .modal__close, .modal__close, .modal__window-wrapper",
            objModalDataAttr: "data-modal-trigger"
        };

        var strDataPopupName = $(this).attr(settings.objModalDataAttr);

        $(document).on("click", settings.objModalPopupBtn, function () {
            if ($(this).attr(settings.objModalDataAttr)) {

                var strDataPopupName = $(this).attr(settings.objModalDataAttr);

                //Fade In Modal Pop Up
                $("#" + strDataPopupName).fadeIn();

                $("body").addClass("no-scroll");

            }
        });


        //On clicking the modal background
        $(document).on("click", settings.objModalCloseBtn, function (e) {
            if ($(e.target).is(".modal, .modal__close, .modal__window-wrapper")) {
                $(".modal").fadeOut();
                if ($("body").hasClass("no-scroll")) {
                    $("body").removeClass("no-scroll");
                }
                return false;
            }
        });


        $(document).keydown(function (e) {
            if (e.keyCode === 27) {
                $(".modal").fadeOut();
                if ($("body").hasClass("no-scroll")) {
                    $("body").removeClass("no-scroll");
                }
            }
        });
    };


    ////////////////////////////////////////
    //  Product and Variation management  //
    ////////////////////////////////////////
    module.productVariationDisable = function () {
        try {
            if (variation_controls.length < 1 || variations.length < 1) {
                // no controls to look at, or possibly no variation info
                return;
            }

            var arrSelectedAttributes = [];
            var arrSelectedOptions = [];

            for (variationControlIndex in variation_controls) {
                if (variation_controls[variationControlIndex].Type === 'RadioButton') {
                    // find the control
                    var rb = document.getElementById(variation_controls[variationControlIndex].Id);

                    // ensure enabled
                    rb.removeAttribute("disabled");

                    if (rb.checked) {
                        // the attribute has a selection
                        arrSelectedAttributes.push(variation_controls[variationControlIndex].Att);
                        arrSelectedOptions.push(variation_controls[variationControlIndex].AttOpt);
                    }
                }
            }

            if (arrSelectedOptions.length < 1) {
                // no variation options selected
                return;
            }

            // process by selected attribute (and its option)
            for (iSelectedAttributeIndex = 0, iSelectedAttributesLength = arrSelectedAttributes.length; iSelectedAttributeIndex < iSelectedAttributesLength; iSelectedAttributeIndex++) {
                var iSelectedAttribute = arrSelectedAttributes[iSelectedAttributeIndex];
                var iSelectedAttributeOption = arrSelectedOptions[iSelectedAttributeIndex];

                var arrAllowedControlIds = [];

                // go over the variations and find ones that contain this variation option
                for (variationIndex in variations) {
                    if (variations[variationIndex].VariableValues.includes(iSelectedAttributeOption) === true) {
                        // the variation includes the selected option so its other variables are allowed
                        for (variationVariableIndex in variations[variationIndex].Variables) {
                            if (variations[variationIndex].Variables[variationVariableIndex].Att !== iSelectedAttribute) {
                                if (arrAllowedControlIds.includes(variations[variationIndex].Variables[variationVariableIndex].ControlId) !== true) {
                                    arrAllowedControlIds.push(variations[variationIndex].Variables[variationVariableIndex].ControlId);
                                }
                            }
                        }
                    }
                }

                // at this point we should have a list of allowed control ids for this attribute selection
                // go over the controls (excluding those for this attribute) and disable if not allowed
                for (variationControlIndex in variation_controls) {
                    if (variation_controls[variationControlIndex].Type === 'RadioButton' && variation_controls[variationControlIndex].Att !== iSelectedAttribute) {
                        if (arrAllowedControlIds.includes(variation_controls[variationControlIndex].Id) !== true) {
                            // control isn't allowed... disable
                            document.getElementById(variation_controls[variationControlIndex].Id).setAttribute("disabled", "");
                        }
                    }
                }
            }
        }
        catch (err) {
            console.log(err);
        }
    };

    module.productVariationGet = function () {
        try {
            if (variation_controls.length < 1 || variations.length < 1) {
                // no controls to look at, or possibly no variation info
                return 0;
            }

            var arrSelectedOptions = [];

            for (var iVariationControlIndex = 0, iVariationControlLength = variation_controls.length; iVariationControlIndex < iVariationControlLength; iVariationControlIndex++) {
                if (variation_controls[iVariationControlIndex].Type === 'RadioButton') {
                    if (document.getElementById(variation_controls[iVariationControlIndex].Id).checked) {
                        arrSelectedOptions.push(parseInt(document.getElementById(variation_controls[iVariationControlIndex].Id).value));
                    }
                }
            }

            if (arrSelectedOptions.length < 1) {
                // no variation options selected
                return 0;
            }

            // loop over variations to try and find a match
            for (var iVariationsIndex = 0, iVariationsLength = variations.length; iVariationsIndex < iVariationsLength; iVariationsIndex++) {
                if (JSON.stringify(arrSelectedOptions.sort()) === JSON.stringify(variations[iVariationsIndex].VariableValues.sort())) {
                    return variations[iVariationsIndex].VariationId;
                }
            }

            // no match found
            return 0;
        }
        catch (err) {
            console.log(err);
            return 0;
        }
    };

    module.produproductVariationSetCB = function (ctrlCheckbox, selectOptions, selectImage, optionSelected, runDisable) {
        // single checkbox select for the group
        $('input[name="' + ctrlCheckbox.name + '"]').not(ctrlCheckbox).prop('checked', false);

        module.productVariationSet(module.productVariationGet(), selectOptions, selectImage, optionSelected, runDisable);
    };

    module.productVariationSet = function (iVariationId, selectOptions, selectImage, optionSelected, runDisable) {
        try {
            if (runDisable) { module.productVariationDisable(); }

            if (iVariationId !== 0) {
                if (variation_controls.length < 1 || variations.length < 1) {
                    // invalid variation id or no variation information, don't do anything
                    module.productVariation_UpdateSelectedOptionImages();
                    return;
                }
            }

            // find the variation
            var bVariationFound = false;

            for (var iVariationsIndex = 0, iVariationsLength = variations.length; iVariationsIndex < iVariationsLength; iVariationsIndex++) {
                if (variations[iVariationsIndex].VariationId === iVariationId) {
                    var selectedVariation = variations[iVariationsIndex];

                    document.getElementById("div_product_stockcode").innerHTML = "<strong>Product code: </strong> " + selectedVariation.StockCode;
                    document.getElementById("div_product_price").innerHTML = selectedVariation.PriceHTML;

                    if (selectedVariation.CDPriceHTML !== "") {
                        document.getElementById("div_product_customerPrice").parentElement.style.removeProperty("display");
                        document.getElementById("div_product_customerPrice").innerHTML = "<div class=\"y-spacer__16\"></div><span class=\"tooltip modal__trigger\" data-modal-trigger=\"modal-att-customerdiscount\">With your customer discount you would pay £" + selectedVariation.CDPriceHTML + " for this product.</span>";
                    }
                    else {
                        document.getElementById("div_product_customerPrice").parentElement.style.display = "none";
                    }

                    if (selectOptions) {
                        // loop over variables and select
                        for (var iVariablesIndex = 0, iVariablesLength = selectedVariation.Variables.length; iVariablesIndex < iVariablesLength; iVariablesIndex++) {

                            if (selectedVariation.Variables[iVariablesIndex].Type === 'RadioButton') {

                                document.getElementById(selectedVariation.Variables[iVariablesIndex].ControlId).checked = true;
                            }
                        }
                    }

                    if (selectImage) {
                        if (selectedVariation.ImageIndex > 0) {
                            $('[data-product-gallery]').flickity('select', selectedVariation.ImageIndex - 1);

                            // reselect the variation incase it got changed by selecting an image
                            module.productVariationSet(iVariationId, true, false, optionSelected, runDisable);
                        }

                        if (optionSelected > 0) {
                            // look up if a specific image for the option selected and show if possible
                            if (window.hasOwnProperty("optionImages")) {
                                for (var iOptionImageIndex = 0, iOptionImageLength = optionImages.length; iOptionImageIndex < iOptionImageLength; iOptionImageIndex++) {
                                    if (optionImages[iOptionImageIndex].OptionId === optionSelected) {

                                        // get the slider
                                        var $carousel = $('[data-product-gallery]').flickity();
                                        var flkty = $carousel.data('flickity');

                                        // get the slides
                                        var slides = $('[data-product-gallery] .slider__item');

                                        var bSlideFound = false;
                                        var iSlideIndex = 0;

                                        for (var iCellIndex = 0, iNoOfCells = slides.length; iCellIndex < iNoOfCells; iCellIndex++) {
                                            if ($(slides[iCellIndex]).find('[image-attribute="' + optionImages[iOptionImageIndex].AttributeId + '"]').length > 0) {
                                                bSlideFound = true;
                                                iSlideIndex = iCellIndex;
                                                break;
                                            }
                                        }

                                        if (bSlideFound) {
                                            // update the image of the slide
                                            $(slides[iCellIndex]).find('[image-attribute="' + optionImages[iOptionImageIndex].AttributeId + '"]').attr('src', optionImages[iOptionImageIndex].ImageUrl);

                                            // select the slide
                                            flkty.select(iSlideIndex);
                                        }
                                        else {
                                            // insert a new slide
                                            var newSlide = $('<div class="slider__item"><img src="' + optionImages[iOptionImageIndex].ImageUrl + '" alt="" image-attribute="' + optionImages[iOptionImageIndex].AttributeId + '"></div>');

                                            flkty.append(newSlide);

                                            flkty.select(flkty.cells.length - 1);
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    bVariationFound = true;

                    // update the basket to be available
                    document.getElementById("btn_Product_AddToBasket").removeAttribute("disabled");
                    document.getElementById("btn_Product_AddToBasket").value = "Add to basket";

                    if (selectedVariation.Available !== true) {
                        // not available
                        // can't be added to the basket
                        document.getElementById("btn_Product_AddToBasket").setAttribute("disabled", "disabled");
                        document.getElementById("btn_Product_AddToBasket").value = "Not available";
                    }

                    break;
                }
            }

            if (bVariationFound !== true) {
                // disable add to basket
                document.getElementById("btn_Product_AddToBasket").setAttribute("disabled", "disabled");
                document.getElementById("btn_Product_AddToBasket").value = "Combination not available";
            }

            if (selectOptions) {
                $('div.product__product__accordion div.is-active').each(function () {
                    $(this).removeClass('is-active');
                });
            }

            // always try and reselect option images
            module.productVariation_UpdateSelectedOptionImages();

            if (runDisable) { module.productVariationDisable(); }
        }
        catch (err) {
            console.log(err);
        }
    };

    module.productVariation_UpdateSelectedOptionImages = function () {
        try {
            if (variation_controls.length < 1 || variations.length < 1) {
                // no controls to look at, or possibly no variation info
                return;
            }

            var strImages = "";

            //var arrSelectedImages = [];

            for (var iVariationControlIndex = 0, iVariationControlLength = variation_controls.length; iVariationControlIndex < iVariationControlLength; iVariationControlIndex++) {
                if (variation_controls[iVariationControlIndex].Type === 'RadioButton') {
                    if (document.getElementById(variation_controls[iVariationControlIndex].Id).checked) {
                        // option selected, look up image for the option
                        if (window.hasOwnProperty("optionImages")) {
                            // option images are present, loop through to look up if image for option exists
                            for (var iOptionImageIndex = 0, iOptionImageLength = optionImages.length; iOptionImageIndex < iOptionImageLength; iOptionImageIndex++) {
                                if (optionImages[iOptionImageIndex].OptionId === parseInt(document.getElementById(variation_controls[iVariationControlIndex].Id).value)) {
                                    // image for option exists, add to array of images to show
                                    strImages += '<img src="' + optionImages[iOptionImageIndex].ImageUrl + '" alt="" class="slider__thumbnails-item" />';

                                    //arrSelectedImages.push('<img src="' + optionImages[iOptionImageIndex].ImageUrl + '" alt="" class="slider__thumbnails-item" />');
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            if (strImages.length > 0) {
                // images selected
                // check if container div exists
                if ($('div.product-item__gallery div.slider__thumbnails').length) {
                    // update contents
                    $('div.product-item__gallery div.slider__thumbnails').html(strImages);
                }
                else {
                    // add content
                    $('div.product-item__gallery').append('<div class="slider__thumbnails">' + strImages + '</div>');
                }
            }
            else {
                // no images selected, remove thumb container
                $('div.product-item__gallery div.slider__thumbnails').remove();
            }
        }
        catch (err) {
            console.log(err);
            return;
        }
    };


    /////////////////////////
    //  Basket management  //
    /////////////////////////
    module.basketUpdateHeaderCount = function () {
        if ($("#header_basketLink").length) {
            $.ajax({
                type: "GET",
                url: "/services/basket.asmx/GetItemsCount",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (msg) {
                    $("#header_basketLink").text('Basket (' + msg.d+')');
                },
                error: function (msg) {
                    
                }
            });
        }
    };

    module.basketAdd = function (productId, variationId, quantity) {
        var myData = { ProductId: productId, VariationId: variationId, Quantity: quantity };

        $.ajax({
            type: "POST",
            url: "/services/basket.asmx/AddItemByIds",
            data: JSON.stringify(myData),
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (msg) {
                switch (msg.d) {
                    case 0:
                        //error
                        module.notification("error", 6000, "We're sorry but we could not add this item to your basket at this time, please try again later.", "basketAdd0" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                        break;

                    case 1:
                        // success
                        module.notification("success", 6000, "Item added to your <a href='/basket/' title='view your basket'>basket</a>.", "basketAdd1" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                        break;

                    case 2:
                        // cannot find
                        module.notification("warning", 6000, "Please select options to add this item to your basket.", "basketAdd2" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                        break;

                    case 3:
                        // not enought stock
                        module.notification("warning", 6000, "We're sorry but we currently do not have enough stock to add to your basket, please reduce the quantity desired and try again.", "basketAdd3" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                        break;

                    case 4:
                        // out of stock
                        module.notification("warning", 6000, "We're sorry but we are currently out of stock of this product.", "basketAdd4" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                        break;
                }

                module.basketUpdateHeaderCount();
            },
            error: function (msg) {
                module.notification("error", 6000, "We're sorry but we could not add this item to your basket at this time, please try again later.", "basketAdd5" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
            }
        });
    };


    //module.reorder_AddToBasket = function (productId, variationId, quantity) {

    //};

    module.reorder_IncreaseQuantity = function (control) {
        var iQty = 0;
        var input = $(control).parents("[data-quantity-wrapper]").find("[data-quantity-input]");
        var currentValue = input.val();

        iQty = Number(currentValue) + 1;
        input.val(iQty).trigger("change");
        return false;
    };

    module.reorder_DecreaseQuantity = function (control) {
        var iQty = 0;
        var input = $(control).parents("[data-quantity-wrapper]").find("[data-quantity-input]");
        var currentValue = input.val();

        if (currentValue >= 1) {
            iQty = Number(currentValue) - 1;
            input.val(iQty).trigger("change");
        }
        return false;
    };

    module.reorder_UpdateQuantity = function (control, productId, variationId, quantity)
    {
        if (control.tagName.toLowerCase() === "button") {
            // assume add to basket button
            // just update quantity of quantity box and trigger change
            $(control).parent().prev().find(".form__quantity-input").val("1").trigger("change");
            //module.reorder_UpdateQuantity($(control).parent().prev().find(".form__quantity-input").get()[0], productId, variationId, quantity);
        }
        else {
            // assume quantity text box
            var myData = { ProductId: productId, VariationId: variationId, Quantity: quantity };

            $.ajax({
                type: "POST",
                url: "/services/basket.asmx/UpdateItemQuantity",
                data: JSON.stringify(myData),
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (msg) {
                    switch (msg.d) {
                        case 0:
                            //error
                            module.notification("error", 6000, "We're sorry but we could not add this item to your basket at this time, please try again later.", "basketAdd0" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                            break;

                        case 1:
                            // success
                            //module.notification("success", 6000, "Item added to your <a href='/basket/' title='view your basket'>basket</a>.", "basketAdd1" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                            break;

                        case 2:
                            // cannot find
                            module.notification("warning", 6000, "Please select options to add this item to your basket.", "basketAdd2" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                            break;

                        case 3:
                            // not enought stock
                            module.notification("warning", 6000, "We're sorry but we currently do not have enough stock to add to your basket, please reduce the quantity desired and try again.", "basketAdd3" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                            break;

                        case 4:
                            // out of stock
                            module.notification("warning", 6000, "We're sorry but we are currently out of stock of this product.", "basketAdd4" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                            break;
                    }

                    var divQuantitySelect = $(control).parent().parent();
                    var divAddToBasket = divQuantitySelect.next();

                    if (quantity > 0) {
                        // need to show quantity box
                        divQuantitySelect.removeClass("is-hidden");

                        if (!divAddToBasket.hasClass("is-hidden")) {
                            divAddToBasket.addClass("is-hidden");
                        }
                    }
                    else {
                        // need to show add to basket button
                        divAddToBasket.removeClass("is-hidden");

                        if (!divQuantitySelect.hasClass("is-hidden")) {
                            divQuantitySelect.addClass("is-hidden");
                        }
                    }

                    module.basketUpdateHeaderCount();
                },
                error: function (msg) {
                    module.notification("error", 6000, "We're sorry but we could not add this item to your basket at this time, please try again later.", "basketAdd5" + productId + variationId + Math.floor((Math.random() * 1000) + 1));
                }
            });
        }
    };


    ///////////////////
    // Scroll Table
    ///////////////////
    module.scrollTable = function () {
        if ($("[data-table-scroll]").length) {

            $("[data-table-scroll]").on("scroll", function () {
                var currentScrollLeftPosition = $(this).scrollLeft();
                var maxScrollPosition = $(this)[0].scrollWidth - $(this).parent().outerWidth();

                if (currentScrollLeftPosition === 0) {
                    $('.scroll-table__shadow-left').fadeOut("fast");
                    if (currentScrollLeftPosition === maxScrollPosition) {
                        $('.scroll-table__shadow-right').fadeOut("fast");
                    }
                    else {
                        $('.scroll-table__shadow-right').fadeIn("fast");
                    }
                }
                else {
                    if (currentScrollLeftPosition === maxScrollPosition) {
                        $('.scroll-scroll-table__shadow-right').fadeOut("fast");
                        $('.scroll-table__shadow-left').fadeIn("fast");
                    } else {
                        $('.scroll-table__shadow-right').fadeIn("fast");
                        $('.scroll-table__shadow-left').fadeIn("fast");
                    }
                }
            });

            $("[data-table-scroll]").trigger("scroll");
            $(window).resize(function () {
                $("[data-table-scroll]").trigger("scroll");
            });

            $(document).on("click", ".scroll-table__shadow", function () {
                var scrollContainer = $(".scroll-table__scroll"),
                    horizontalScroll;
                if ($(this).hasClass("scroll-table__shadow-right")) {
                    horizontalScroll = 200 + scrollContainer.scrollLeft();
                    scrollContainer.animate({
                        scrollLeft: horizontalScroll
                    });
                } else if ($(this).hasClass("scroll-table__shadow-left")) {
                    horizontalScroll = 200 - scrollContainer.scrollLeft();
                    scrollContainer.animate({
                        scrollLeft: -horizontalScroll
                    });
                }
            });
        }
    };

}(window.module = window.module || {}, jQuery));


//////////////////////////////////////////                              
////  Document Ready                                  
//////////////////////////////////////////
$(document).ready(function () {
    // All Pages
    module.detectDevice();
    module.backToTop();
    module.scrollTo();
    module.bespokeCTA();
    module.homeProductBrandSlider();
    module.homeServiceSlider();
    module.heroSlider();
    module.homeJournalSlider();
    module.productSlider();
    module.megaMenu();
    module.search();
    module.quantitiyInput();
    module.imageSlider();
    module.categorySlider();
    module.navigation();
    module.map();
    //module.readingTimeArticle();
    //module.readingTime();
    module.newsletterForm();
    module.stockistMap();
    module.accordion();
    module.UpgradeASPNETValidation();
    module.modal();
    module.scrollTable();
    module.menu();
    svg4everybody();
});






$(document).ready(function () {
    variationAccordionOnload();
});

function variationAccordionOnload() {
    // Loop through varition categories
    $("[data-variation-select]").each(function () {

        // Set default variables
        var open = true;

        // badge removed 1/11/19 on sti request
        //var selectedOption = "";

        // Find all variations and loop thorugh
        $(this).find("input[type='radio']").each(function () {

            // Set to open if 
            if ($(this).is(':checked') === true) {
                open = false;

                // badge removed 1/11/19 on sti request
                //selectedOption = $(this).next().text();

                // badge removed 1/11/19 on sti request
                //// Update badge to show selected variatant
                //$(this).parents(".product__accordion-content").prev().find("[data-variation-header-badge]").html(selectedOption).removeClass("is-hidden");
            }
        });

        // IF true, show current accordion
        if (open === true) {
            $(this).parents(".product__accordion-content").addClass('is-active');
            $(this).parents(".product__accordion-content").prev().addClass('is-active');
        }

    });
}

$(document).on('click', '.product__accordion-header > div', function (e) {

    //If the trigger has the current class, 
    if ($(this).parent().hasClass('is-active')) {
        //Slide up it's content panel and remove current class
        $(this).parent().next().removeClass('is-active');
        $(this).parent().removeClass('is-active');
    }
    else {
        //Slide down the content panel we need.
        $(this).parent().next().addClass('is-active');
        //Add the current class to the trigger
        $(this).parent().addClass('is-active');
    }

    //e.preventDefault();
    e.stopImmediatePropagation();
    return false;

});


// badge removed 1/11/19 on sti request
//$(document).on('click', '[data-variation-select] > .form__variation-box > label', function (e) {

//    // GET VARIATION NAME SO WE CAN ADD TO BADGE LATER ON
//    var getVariationName = $(this).text();

//    // UPDATE BADGE TEXT USING VARITATION NAME
//    $(this).parents(".product__accordion-content ").prev().find("[data-variation-header-badge]").html(getVariationName).removeClass("is-hidden");

//    // OPEN / CLOSE ACCORDION ELEMENTS
//    //$(this).parents(".product__accordion-content ").prev().removeClass('is-active');
//    //$(this).parents(".product__accordion-content ").removeClass('is-active');
//    //$(this).parents(".product__accordion-content ").next().addClass('is-active');
//    //$(this).parents(".product__accordion-content ").next().next().addClass('is-active');
//});
























//////////////////////////////////////////                              
////                    
//// Testing Purposes only.
////                              
//////////////////////////////////////////

if (inProduction === "false") {

	$(document).click(function (event) {
		console.log($(event.target).attr('class'));
	});

	var docWidth = document.documentElement.offsetWidth;

	[].forEach.call(
		document.querySelectorAll('*'),
		function (el) {
			if (el.offsetWidth > docWidth) {
				console.log(el);
				$(el).css('border', '1px solid #ff00ff');
			}
		}
	);
};
