PageSourceSearch

https://racemob.com/assets/searches_fast_bundle-f4280bc76f5a450a4b…45ca5e3849412a740973fb530c314df2dffbb898.js

js racemob.com collected 2026-10-05 01:50:20 UTC 1,339,473 bytes, 45,306 lines download raw bytes

vendor: 5,218 bytes, lines 1-195
1/*!
2 * jQuery JavaScript Library v3.5.1
3 * https://jquery.com/
4 *
5 * Includes Sizzle.js
6 * https://sizzlejs.com/
7 *
8 * Copyright JS Foundation and other contributors
9 * Released under the MIT license
10 * https://jquery.org/license
11 *
12 * Date: 2020-05-04T22:49Z
13 */
14
15( function( global, factory ) {
16
17	"use strict";
18
19	if ( typeof module === "object" && typeof module.exports === "object" ) {
20
21		// For CommonJS and CommonJS-like environments where a proper `window`
22		// is present, execute the factory and get jQuery.
23		// For environments that do not have a `window` with a `document`
24		// (such as Node.js), expose a factory as module.exports.
25		// This accentuates the need for the creation of a real `window`.
26		// e.g. var jQuery = require("jquery")(window);
27		// See ticket #14549 for more info.
28		module.exports = global.document ?
29			factory( global, true ) :
30			function( w ) {
31				if ( !w.document ) {
32					throw new Error( "jQuery requires a window with a document" );
33				}
34				return factory( w );
35			};
36	} else {
37		factory( global );
38	}
39
40// Pass this if window is not defined yet
41} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
42
43// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
44// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
45// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
46// enough that all such attempts are guarded in a try block.
47"use strict";
48
49var arr = [];
50
51var getProto = Object.getPrototypeOf;
52
53var slice = arr.slice;
54
55var flat = arr.flat ? function( array ) {
56	return arr.flat.call( array );
57} : function( array ) {
58	return arr.concat.apply( [], array );
59};
60
61
62var push = arr.push;
63
64var indexOf = arr.indexOf;
65
66var class2type = {};
67
68var toString = class2type.toString;
69
70var hasOwn = class2type.hasOwnProperty;
71
72var fnToString = hasOwn.toString;
73
74var ObjectFunctionString = fnToString.call( Object );
75
76var support = {};
77
78var isFunction = function isFunction( obj ) {
79
80      // Support: Chrome <=57, Firefox <=52
81      // In some browsers, typeof returns "function" for HTML <object> elements
82      // (i.e., `typeof document.createElement( "object" ) === "function"`).
83      // We don't want to classify *any* DOM node as a function.
84      return typeof obj === "function" && typeof obj.nodeType !== "number";
85  };
86
87
88var isWindow = function isWindow( obj ) {
89		return obj != null && obj === obj.window;
90	};
91
92
93var document = window.document;
94
95
96
97	var preservedScriptAttributes = {
98		type: true,
99		src: true,
100		nonce: true,
101		noModule: true
102	};
103
104	function DOMEval( code, node, doc ) {
105		doc = doc || document;
106
107		var i, val,
108			script = doc.createElement( "script" );
109
110		script.text = code;
111		if ( node ) {
112			for ( i in preservedScriptAttributes ) {
113
114				// Support: Firefox 64+, Edge 18+
115				// Some browsers don't support the "nonce" property on scripts.
116				// On the other hand, just using `getAttribute` is not enough as
117				// the `nonce` attribute is reset to an empty string whenever it
118				// becomes browsing-context connected.
119				// See https://github.com/whatwg/html/issues/2369
120				// See https://html.spec.whatwg.org/#nonce-attributes
121				// The `node.getAttribute` check was added for the sake of
122				// `jQuery.globalEval` so that it can fake a nonce-containing node
123				// via an object.
124				val = node[ i ] || node.getAttribute && node.getAttribute( i );
125				if ( val ) {
126					script.setAttribute( i, val );
127				}
128			}
129		}
130		doc.head.appendChild( script ).parentNode.removeChild( script );
131	}
132
133
134function toType( obj ) {
135	if ( obj == null ) {
136		return obj + "";
137	}
138
139	// Support: Android <=2.3 only (functionish RegExp)
140	return typeof obj === "object" || typeof obj === "function" ?
141		class2type[ toString.call( obj ) ] || "object" :
142		typeof obj;
143}
144/* global Symbol */
145// Defining this global in .eslintrc.json would create a danger of using the global
146// unguarded in another place, it seems safer to define global only for this module
147
148
149
150var
151	version = "3.5.1",
152
153	// Define a local copy of jQuery
154	jQuery = function( selector, context ) {
155
156		// The jQuery object is actually just the init constructor 'enhanced'
157		// Need init if jQuery is called (just allow error to be thrown if not included)
158		return new jQuery.fn.init( selector, context );
159	};
160
161jQuery.fn = jQuery.prototype = {
162
163	// The current version of jQuery being used
164	jquery: version,
165
166	constructor: jQuery,
167
168	// The default length of a jQuery object is 0
169	length: 0,
170
171	toArray: function() {
172		return slice.call( this );
173	},
174
175	// Get the Nth element in the matched element set OR
176	// Get the whole matched element set as a clean array
177	get: function( num ) {
178
179		// Return all the elements in a clean array
180		if ( num == null ) {
181			return slice.call( this );
182		}
183
184		// Return just the one element from the set
185		return num < 0 ? this[ num + this.length ] : this[ num ];
186	},
187
188	// Take an array of elements and push it onto the stack
189	// (returning the new matched element set)
190	pushStack: function( elems ) {
191
192		// Build a new jQuery matched element set
193		var ret = jQuery.merge( this.constructor(), elems );
194
195		// Add the old object onto the stack (as a reference)
vendor: 6,004 bytes, lines 196-466
196		ret.prevObject = this;
197
198		// Return the newly-formed element set
199		return ret;
200	},
201
202	// Execute a callback for every element in the matched set.
203	each: function( callback ) {
204		return jQuery.each( this, callback );
205	},
206
207	map: function( callback ) {
208		return this.pushStack( jQuery.map( this, function( elem, i ) {
209			return callback.call( elem, i, elem );
210		} ) );
211	},
212
213	slice: function() {
214		return this.pushStack( slice.apply( this, arguments ) );
215	},
216
217	first: function() {
218		return this.eq( 0 );
219	},
220
221	last: function() {
222		return this.eq( -1 );
223	},
224
225	even: function() {
226		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
227			return ( i + 1 ) % 2;
228		} ) );
229	},
230
231	odd: function() {
232		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
233			return i % 2;
234		} ) );
235	},
236
237	eq: function( i ) {
238		var len = this.length,
239			j = +i + ( i < 0 ? len : 0 );
240		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
241	},
242
243	end: function() {
244		return this.prevObject || this.constructor();
245	},
246
247	// For internal use only.
248	// Behaves like an Array's method, not like a jQuery method.
249	push: push,
250	sort: arr.sort,
251	splice: arr.splice
252};
253
254jQuery.extend = jQuery.fn.extend = function() {
255	var options, name, src, copy, copyIsArray, clone,
256		target = arguments[ 0 ] || {},
257		i = 1,
258		length = arguments.length,
259		deep = false;
260
261	// Handle a deep copy situation
262	if ( typeof target === "boolean" ) {
263		deep = target;
264
265		// Skip the boolean and the target
266		target = arguments[ i ] || {};
267		i++;
268	}
269
270	// Handle case when target is a string or something (possible in deep copy)
271	if ( typeof target !== "object" && !isFunction( target ) ) {
272		target = {};
273	}
274
275	// Extend jQuery itself if only one argument is passed
276	if ( i === length ) {
277		target = this;
278		i--;
279	}
280
281	for ( ; i < length; i++ ) {
282
283		// Only deal with non-null/undefined values
284		if ( ( options = arguments[ i ] ) != null ) {
285
286			// Extend the base object
287			for ( name in options ) {
288				copy = options[ name ];
289
290				// Prevent Object.prototype pollution
291				// Prevent never-ending loop
292				if ( name === "__proto__" || target === copy ) {
293					continue;
294				}
295
296				// Recurse if we're merging plain objects or arrays
297				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
298					( copyIsArray = Array.isArray( copy ) ) ) ) {
299					src = target[ name ];
300
301					// Ensure proper type for the source value
302					if ( copyIsArray && !Array.isArray( src ) ) {
303						clone = [];
304					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
305						clone = {};
306					} else {
307						clone = src;
308					}
309					copyIsArray = false;
310
311					// Never move original objects, clone them
312					target[ name ] = jQuery.extend( deep, clone, copy );
313
314				// Don't bring in undefined values
315				} else if ( copy !== undefined ) {
316					target[ name ] = copy;
317				}
318			}
319		}
320	}
321
322	// Return the modified object
323	return target;
324};
325
326jQuery.extend( {
327
328	// Unique for each copy of jQuery on the page
329	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
330
331	// Assume jQuery is ready without the ready module
332	isReady: true,
333
334	error: function( msg ) {
335		throw new Error( msg );
336	},
337
338	noop: function() {},
339
340	isPlainObject: function( obj ) {
341		var proto, Ctor;
342
343		// Detect obvious negatives
344		// Use toString instead of jQuery.type to catch host objects
345		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
346			return false;
347		}
348
349		proto = getProto( obj );
350
351		// Objects with no prototype (e.g., `Object.create( null )`) are plain
352		if ( !proto ) {
353			return true;
354		}
355
356		// Objects with prototype are plain iff they were constructed by a global Object function
357		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
358		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
359	},
360
361	isEmptyObject: function( obj ) {
362		var name;
363
364		for ( name in obj ) {
365			return false;
366		}
367		return true;
368	},
369
370	// Evaluates a script in a provided context; falls back to the global one
371	// if not specified.
372	globalEval: function( code, options, doc ) {
373		DOMEval( code, { nonce: options && options.nonce }, doc );
374	},
375
376	each: function( obj, callback ) {
377		var length, i = 0;
378
379		if ( isArrayLike( obj ) ) {
380			length = obj.length;
381			for ( ; i < length; i++ ) {
382				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
383					break;
384				}
385			}
386		} else {
387			for ( i in obj ) {
388				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
389					break;
390				}
391			}
392		}
393
394		return obj;
395	},
396
397	// results is for internal usage only
398	makeArray: function( arr, results ) {
399		var ret = results || [];
400
401		if ( arr != null ) {
402			if ( isArrayLike( Object( arr ) ) ) {
403				jQuery.merge( ret,
404					typeof arr === "string" ?
405					[ arr ] : arr
406				);
407			} else {
408				push.call( ret, arr );
409			}
410		}
411
412		return ret;
413	},
414
415	inArray: function( elem, arr, i ) {
416		return arr == null ? -1 : indexOf.call( arr, elem, i );
417	},
418
419	// Support: Android <=4.0 only, PhantomJS 1 only
420	// push.apply(_, arraylike) throws on ancient WebKit
421	merge: function( first, second ) {
422		var len = +second.length,
423			j = 0,
424			i = first.length;
425
426		for ( ; j < len; j++ ) {
427			first[ i++ ] = second[ j ];
428		}
429
430		first.length = i;
431
432		return first;
433	},
434
435	grep: function( elems, callback, invert ) {
436		var callbackInverse,
437			matches = [],
438			i = 0,
439			length = elems.length,
440			callbackExpect = !invert;
441
442		// Go through the array, only saving the items
443		// that pass the validator function
444		for ( ; i < length; i++ ) {
445			callbackInverse = !callback( elems[ i ], i );
446			if ( callbackInverse !== callbackExpect ) {
447				matches.push( elems[ i ] );
448			}
449		}
450
451		return matches;
452	},
453
454	// arg is for internal usage only
455	map: function( elems, callback, arg ) {
456		var length, value,
457			i = 0,
458			ret = [];
459
460		// Go through the array, translating each of the items to their new values
461		if ( isArrayLike( elems ) ) {
462			length = elems.length;
463			for ( ; i < length; i++ ) {
464				value = callback( elems[ i ], i, arg );
465
466				if ( value != null ) {
vendor: 1,491 bytes, lines 467-529
467					ret.push( value );
468				}
469			}
470
471		// Go through every key on the object,
472		} else {
473			for ( i in elems ) {
474				value = callback( elems[ i ], i, arg );
475
476				if ( value != null ) {
477					ret.push( value );
478				}
479			}
480		}
481
482		// Flatten any nested arrays
483		return flat( ret );
484	},
485
486	// A global GUID counter for objects
487	guid: 1,
488
489	// jQuery.support is not used in Core but other projects attach their
490	// properties to it so it needs to exist.
491	support: support
492} );
493
494if ( typeof Symbol === "function" ) {
495	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
496}
497
498// Populate the class2type map
499jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
500function( _i, name ) {
501	class2type[ "[object " + name + "]" ] = name.toLowerCase();
502} );
503
504function isArrayLike( obj ) {
505
506	// Support: real iOS 8.2 only (not reproducible in simulator)
507	// `in` check used to prevent JIT error (gh-2145)
508	// hasOwn isn't used here due to false negatives
509	// regarding Nodelist length in IE
510	var length = !!obj && "length" in obj && obj.length,
511		type = toType( obj );
512
513	if ( isFunction( obj ) || isWindow( obj ) ) {
514		return false;
515	}
516
517	return type === "array" || length === 0 ||
518		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
519}
520var Sizzle =
521/*!
522 * Sizzle CSS Selector Engine v2.3.5
523 * https://sizzlejs.com/
524 *
525 * Copyright JS Foundation and other contributors
526 * Released under the MIT license
527 * https://js.foundation/
528 *
529 * Date: 2020-03-14
vendor: 16,386 bytes, lines 529-1112
529
530 */
531( function( window ) {
532var i,
533	support,
534	Expr,
535	getText,
536	isXML,
537	tokenize,
538	compile,
539	select,
540	outermostContext,
541	sortInput,
542	hasDuplicate,
543
544	// Local document vars
545	setDocument,
546	document,
547	docElem,
548	documentIsHTML,
549	rbuggyQSA,
550	rbuggyMatches,
551	matches,
552	contains,
553
554	// Instance-specific data
555	expando = "sizzle" + 1 * new Date(),
556	preferredDoc = window.document,
557	dirruns = 0,
558	done = 0,
559	classCache = createCache(),
560	tokenCache = createCache(),
561	compilerCache = createCache(),
562	nonnativeSelectorCache = createCache(),
563	sortOrder = function( a, b ) {
564		if ( a === b ) {
565			hasDuplicate = true;
566		}
567		return 0;
568	},
569
570	// Instance methods
571	hasOwn = ( {} ).hasOwnProperty,
572	arr = [],
573	pop = arr.pop,
574	pushNative = arr.push,
575	push = arr.push,
576	slice = arr.slice,
577
578	// Use a stripped-down indexOf as it's faster than native
579	// https://jsperf.com/thor-indexof-vs-for/5
580	indexOf = function( list, elem ) {
581		var i = 0,
582			len = list.length;
583		for ( ; i < len; i++ ) {
584			if ( list[ i ] === elem ) {
585				return i;
586			}
587		}
588		return -1;
589	},
590
591	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" +
592		"ismap|loop|multiple|open|readonly|required|scoped",
593
594	// Regular expressions
595
596	// http://www.w3.org/TR/css3-selectors/#whitespace
597	whitespace = "[\\x20\\t\\r\\n\\f]",
598
599	// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
600	identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
601		"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
602
603	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
604	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
605
606		// Operator (capture 2)
607		"*([*^$|!~]?=)" + whitespace +
608
609		// "Attribute values must be CSS identifiers [capture 5]
610		// or strings [capture 3 or capture 4]"
611		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
612		whitespace + "*\\]",
613
614	pseudos = ":(" + identifier + ")(?:\\((" +
615
616		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
617		// 1. quoted (capture 3; capture 4 or capture 5)
618		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
619
620		// 2. simple (capture 6)
621		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
622
623		// 3. anything else (capture 2)
624		".*" +
625		")\\)|)",
626
627	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
628	rwhitespace = new RegExp( whitespace + "+", "g" ),
629	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" +
630		whitespace + "+$", "g" ),
631
632	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
633	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace +
634		"*" ),
635	rdescend = new RegExp( whitespace + "|>" ),
636
637	rpseudo = new RegExp( pseudos ),
638	ridentifier = new RegExp( "^" + identifier + "$" ),
639
640	matchExpr = {
641		"ID": new RegExp( "^#(" + identifier + ")" ),
642		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
643		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
644		"ATTR": new RegExp( "^" + attributes ),
645		"PSEUDO": new RegExp( "^" + pseudos ),
646		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
647			whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
648			whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
649		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
650
651		// For use in libraries implementing .is()
652		// We use this for POS matching in `select`
653		"needsContext": new RegExp( "^" + whitespace +
654			"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
655			"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
656	},
657
658	rhtml = /HTML$/i,
659	rinputs = /^(?:input|select|textarea|button)$/i,
660	rheader = /^h\d$/i,
661
662	rnative = /^[^{]+\{\s*\[native \w/,
663
664	// Easily-parseable/retrievable ID or TAG or CLASS selectors
665	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
666
667	rsibling = /[+~]/,
668
669	// CSS escapes
670	// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
671	runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ),
672	funescape = function( escape, nonHex ) {
673		var high = "0x" + escape.slice( 1 ) - 0x10000;
674
675		return nonHex ?
676
677			// Strip the backslash prefix from a non-hex escape sequence
678			nonHex :
679
680			// Replace a hexadecimal escape sequence with the encoded Unicode code point
681			// Support: IE <=11+
682			// For values outside the Basic Multilingual Plane (BMP), manually construct a
683			// surrogate pair
684			high < 0 ?
685				String.fromCharCode( high + 0x10000 ) :
686				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
687	},
688
689	// CSS string/identifier serialization
690	// https://drafts.csswg.org/cssom/#common-serializing-idioms
691	rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
692	fcssescape = function( ch, asCodePoint ) {
693		if ( asCodePoint ) {
694
695			// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
696			if ( ch === "\0" ) {
697				return "\uFFFD";
698			}
699
700			// Control characters and (dependent upon position) numbers get escaped as code points
701			return ch.slice( 0, -1 ) + "\\" +
702				ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
703		}
704
705		// Other potentially-special ASCII characters get backslash-escaped
706		return "\\" + ch;
707	},
708
709	// Used for iframes
710	// See setDocument()
711	// Removing the function wrapper causes a "Permission Denied"
712	// error in IE
713	unloadHandler = function() {
714		setDocument();
715	},
716
717	inDisabledFieldset = addCombinator(
718		function( elem ) {
719			return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
720		},
721		{ dir: "parentNode", next: "legend" }
722	);
723
724// Optimize for push.apply( _, NodeList )
725try {
726	push.apply(
727		( arr = slice.call( preferredDoc.childNodes ) ),
728		preferredDoc.childNodes
729	);
730
731	// Support: Android<4.0
732	// Detect silently failing push.apply
733	// eslint-disable-next-line no-unused-expressions
734	arr[ preferredDoc.childNodes.length ].nodeType;
735} catch ( e ) {
736	push = { apply: arr.length ?
737
738		// Leverage slice if possible
739		function( target, els ) {
740			pushNative.apply( target, slice.call( els ) );
741		} :
742
743		// Support: IE<9
744		// Otherwise append directly
745		function( target, els ) {
746			var j = target.length,
747				i = 0;
748
749			// Can't trust NodeList.length
750			while ( ( target[ j++ ] = els[ i++ ] ) ) {}
751			target.length = j - 1;
752		}
753	};
754}
755
756function Sizzle( selector, context, results, seed ) {
757	var m, i, elem, nid, match, groups, newSelector,
758		newContext = context && context.ownerDocument,
759
760		// nodeType defaults to 9, since context defaults to document
761		nodeType = context ? context.nodeType : 9;
762
763	results = results || [];
764
765	// Return early from calls with invalid selector or context
766	if ( typeof selector !== "string" || !selector ||
767		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
768
769		return results;
770	}
771
772	// Try to shortcut find operations (as opposed to filters) in HTML documents
773	if ( !seed ) {
774		setDocument( context );
775		context = context || document;
776
777		if ( documentIsHTML ) {
778
779			// If the selector is sufficiently simple, try using a "get*By*" DOM method
780			// (excepting DocumentFragment context, where the methods don't exist)
781			if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
782
783				// ID selector
784				if ( ( m = match[ 1 ] ) ) {
785
786					// Document context
787					if ( nodeType === 9 ) {
788						if ( ( elem = context.getElementById( m ) ) ) {
789
790							// Support: IE, Opera, Webkit
791							// TODO: identify versions
792							// getElementById can match elements by name instead of ID
793							if ( elem.id === m ) {
794								results.push( elem );
795								return results;
796							}
797						} else {
798							return results;
799						}
800
801					// Element context
802					} else {
803
804						// Support: IE, Opera, Webkit
805						// TODO: identify versions
806						// getElementById can match elements by name instead of ID
807						if ( newContext && ( elem = newContext.getElementById( m ) ) &&
808							contains( context, elem ) &&
809							elem.id === m ) {
810
811							results.push( elem );
812							return results;
813						}
814					}
815
816				// Type selector
817				} else if ( match[ 2 ] ) {
818					push.apply( results, context.getElementsByTagName( selector ) );
819					return results;
820
821				// Class selector
822				} else if ( ( m = match[ 3 ] ) && support.getElementsByClassName &&
823					context.getElementsByClassName ) {
824
825					push.apply( results, context.getElementsByClassName( m ) );
826					return results;
827				}
828			}
829
830			// Take advantage of querySelectorAll
831			if ( support.qsa &&
832				!nonnativeSelectorCache[ selector + " " ] &&
833				( !rbuggyQSA || !rbuggyQSA.test( selector ) ) &&
834
835				// Support: IE 8 only
836				// Exclude object elements
837				( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) {
838
839				newSelector = selector;
840				newContext = context;
841
842				// qSA considers elements outside a scoping root when evaluating child or
843				// descendant combinators, which is not what we want.
844				// In such cases, we work around the behavior by prefixing every selector in the
845				// list with an ID selector referencing the scope context.
846				// The technique has to be used as well when a leading combinator is used
847				// as such selectors are not recognized by querySelectorAll.
848				// Thanks to Andrew Dupont for this technique.
849				if ( nodeType === 1 &&
850					( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
851
852					// Expand context for sibling selectors
853					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
854						context;
855
856					// We can use :scope instead of the ID hack if the browser
857					// supports it & if we're not changing the context.
858					if ( newContext !== context || !support.scope ) {
859
860						// Capture the context ID, setting it first if necessary
861						if ( ( nid = context.getAttribute( "id" ) ) ) {
862							nid = nid.replace( rcssescape, fcssescape );
863						} else {
864							context.setAttribute( "id", ( nid = expando ) );
865						}
866					}
867
868					// Prefix every selector in the list
869					groups = tokenize( selector );
870					i = groups.length;
871					while ( i-- ) {
872						groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
873							toSelector( groups[ i ] );
874					}
875					newSelector = groups.join( "," );
876				}
877
878				try {
879					push.apply( results,
880						newContext.querySelectorAll( newSelector )
881					);
882					return results;
883				} catch ( qsaError ) {
884					nonnativeSelectorCache( selector, true );
885				} finally {
886					if ( nid === expando ) {
887						context.removeAttribute( "id" );
888					}
889				}
890			}
891		}
892	}
893
894	// All others
895	return select( selector.replace( rtrim, "$1" ), context, results, seed );
896}
897
898/**
899 * Create key-value caches of limited size
900 * @returns {function(string, object)} Returns the Object data after storing it on itself with
901 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
902 *	deleting the oldest entry
903 */
904function createCache() {
905	var keys = [];
906
907	function cache( key, value ) {
908
909		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
910		if ( keys.push( key + " " ) > Expr.cacheLength ) {
911
912			// Only keep the most recent entries
913			delete cache[ keys.shift() ];
914		}
915		return ( cache[ key + " " ] = value );
916	}
917	return cache;
918}
919
920/**
921 * Mark a function for special use by Sizzle
922 * @param {Function} fn The function to mark
923 */
924function markFunction( fn ) {
925	fn[ expando ] = true;
926	return fn;
927}
928
929/**
930 * Support testing using an element
931 * @param {Function} fn Passed the created element and returns a boolean result
932 */
933function assert( fn ) {
934	var el = document.createElement( "fieldset" );
935
936	try {
937		return !!fn( el );
938	} catch ( e ) {
939		return false;
940	} finally {
941
942		// Remove from its parent by default
943		if ( el.parentNode ) {
944			el.parentNode.removeChild( el );
945		}
946
947		// release memory in IE
948		el = null;
949	}
950}
951
952/**
953 * Adds the same handler for all of the specified attrs
954 * @param {String} attrs Pipe-separated list of attributes
955 * @param {Function} handler The method that will be applied
956 */
957function addHandle( attrs, handler ) {
958	var arr = attrs.split( "|" ),
959		i = arr.length;
960
961	while ( i-- ) {
962		Expr.attrHandle[ arr[ i ] ] = handler;
963	}
964}
965
966/**
967 * Checks document order of two siblings
968 * @param {Element} a
969 * @param {Element} b
970 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
971 */
972function siblingCheck( a, b ) {
973	var cur = b && a,
974		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
975			a.sourceIndex - b.sourceIndex;
976
977	// Use IE sourceIndex if available on both nodes
978	if ( diff ) {
979		return diff;
980	}
981
982	// Check if b follows a
983	if ( cur ) {
984		while ( ( cur = cur.nextSibling ) ) {
985			if ( cur === b ) {
986				return -1;
987			}
988		}
989	}
990
991	return a ? 1 : -1;
992}
993
994/**
995 * Returns a function to use in pseudos for input types
996 * @param {String} type
997 */
998function createInputPseudo( type ) {
999	return function( elem ) {
1000		var name = elem.nodeName.toLowerCase();
1001		return name === "input" && elem.type === type;
1002	};
1003}
1004
1005/**
1006 * Returns a function to use in pseudos for buttons
1007 * @param {String} type
1008 */
1009function createButtonPseudo( type ) {
1010	return function( elem ) {
1011		var name = elem.nodeName.toLowerCase();
1012		return ( name === "input" || name === "button" ) && elem.type === type;
1013	};
1014}
1015
1016/**
1017 * Returns a function to use in pseudos for :enabled/:disabled
1018 * @param {Boolean} disabled true for :disabled; false for :enabled
1019 */
1020function createDisabledPseudo( disabled ) {
1021
1022	// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
1023	return function( elem ) {
1024
1025		// Only certain elements can match :enabled or :disabled
1026		// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
1027		// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
1028		if ( "form" in elem ) {
1029
1030			// Check for inherited disabledness on relevant non-disabled elements:
1031			// * listed form-associated elements in a disabled fieldset
1032			//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
1033			//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
1034			// * option elements in a disabled optgroup
1035			//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
1036			// All such elements have a "form" property.
1037			if ( elem.parentNode && elem.disabled === false ) {
1038
1039				// Option elements defer to a parent optgroup if present
1040				if ( "label" in elem ) {
1041					if ( "label" in elem.parentNode ) {
1042						return elem.parentNode.disabled === disabled;
1043					} else {
1044						return elem.disabled === disabled;
1045					}
1046				}
1047
1048				// Support: IE 6 - 11
1049				// Use the isDisabled shortcut property to check for disabled fieldset ancestors
1050				return elem.isDisabled === disabled ||
1051
1052					// Where there is no isDisabled, check manually
1053					/* jshint -W018 */
1054					elem.isDisabled !== !disabled &&
1055					inDisabledFieldset( elem ) === disabled;
1056			}
1057
1058			return elem.disabled === disabled;
1059
1060		// Try to winnow out elements that can't be disabled before trusting the disabled property.
1061		// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
1062		// even exist on them, let alone have a boolean value.
1063		} else if ( "label" in elem ) {
1064			return elem.disabled === disabled;
1065		}
1066
1067		// Remaining elements are neither :enabled nor :disabled
1068		return false;
1069	};
1070}
1071
1072/**
1073 * Returns a function to use in pseudos for positionals
1074 * @param {Function} fn
1075 */
1076function createPositionalPseudo( fn ) {
1077	return markFunction( function( argument ) {
1078		argument = +argument;
1079		return markFunction( function( seed, matches ) {
1080			var j,
1081				matchIndexes = fn( [], seed.length, argument ),
1082				i = matchIndexes.length;
1083
1084			// Match elements found at the specified indexes
1085			while ( i-- ) {
1086				if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
1087					seed[ j ] = !( matches[ j ] = seed[ j ] );
1088				}
1089			}
1090		} );
1091	} );
1092}
1093
1094/**
1095 * Checks a node for validity as a Sizzle context
1096 * @param {Element|Object=} context
1097 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
1098 */
1099function testContext( context ) {
1100	return context && typeof context.getElementsByTagName !== "undefined" && context;
1101}
1102
1103// Expose support vars for convenience
1104support = Sizzle.support = {};
1105
1106/**
1107 * Detects XML nodes
1108 * @param {Element|Object} elem An element or a document
1109 * @returns {Boolean} True iff elem is a non-HTML XML node
1110 */
1111isXML = Sizzle.isXML = function( elem ) {
1112	var namespace = elem
1112.namespaceURI,
1113		docElem =
vendor: 54,357 bytes, lines 1113-3027
1113 ( elem.ownerDocument || elem ).documentElement;
1114
1115	// Support: IE <=8
1116	// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
1117	// https://bugs.jquery.com/ticket/4833
1118	return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
1119};
1120
1121/**
1122 * Sets document-related variables once based on the current document
1123 * @param {Element|Object} [doc] An element or document object to use to set the document
1124 * @returns {Object} Returns the current document
1125 */
1126setDocument = Sizzle.setDocument = function( node ) {
1127	var hasCompare, subWindow,
1128		doc = node ? node.ownerDocument || node : preferredDoc;
1129
1130	// Return early if doc is invalid or already selected
1131	// Support: IE 11+, Edge 17 - 18+
1132	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1133	// two documents; shallow comparisons work.
1134	// eslint-disable-next-line eqeqeq
1135	if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
1136		return document;
1137	}
1138
1139	// Update global variables
1140	document = doc;
1141	docElem = document.documentElement;
1142	documentIsHTML = !isXML( document );
1143
1144	// Support: IE 9 - 11+, Edge 12 - 18+
1145	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
1146	// Support: IE 11+, Edge 17 - 18+
1147	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1148	// two documents; shallow comparisons work.
1149	// eslint-disable-next-line eqeqeq
1150	if ( preferredDoc != document &&
1151		( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
1152
1153		// Support: IE 11, Edge
1154		if ( subWindow.addEventListener ) {
1155			subWindow.addEventListener( "unload", unloadHandler, false );
1156
1157		// Support: IE 9 - 10 only
1158		} else if ( subWindow.attachEvent ) {
1159			subWindow.attachEvent( "onunload", unloadHandler );
1160		}
1161	}
1162
1163	// Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only,
1164	// Safari 4 - 5 only, Opera <=11.6 - 12.x only
1165	// IE/Edge & older browsers don't support the :scope pseudo-class.
1166	// Support: Safari 6.0 only
1167	// Safari 6.0 supports :scope but it's an alias of :root there.
1168	support.scope = assert( function( el ) {
1169		docElem.appendChild( el ).appendChild( document.createElement( "div" ) );
1170		return typeof el.querySelectorAll !== "undefined" &&
1171			!el.querySelectorAll( ":scope fieldset div" ).length;
1172	} );
1173
1174	/* Attributes
1175	---------------------------------------------------------------------- */
1176
1177	// Support: IE<8
1178	// Verify that getAttribute really returns attributes and not properties
1179	// (excepting IE8 booleans)
1180	support.attributes = assert( function( el ) {
1181		el.className = "i";
1182		return !el.getAttribute( "className" );
1183	} );
1184
1185	/* getElement(s)By*
1186	---------------------------------------------------------------------- */
1187
1188	// Check if getElementsByTagName("*") returns only elements
1189	support.getElementsByTagName = assert( function( el ) {
1190		el.appendChild( document.createComment( "" ) );
1191		return !el.getElementsByTagName( "*" ).length;
1192	} );
1193
1194	// Support: IE<9
1195	support.getElementsByClassName = rnative.test( document.getElementsByClassName );
1196
1197	// Support: IE<10
1198	// Check if getElementById returns elements by name
1199	// The broken getElementById methods don't pick up programmatically-set names,
1200	// so use a roundabout getElementsByName test
1201	support.getById = assert( function( el ) {
1202		docElem.appendChild( el ).id = expando;
1203		return !document.getElementsByName || !document.getElementsByName( expando ).length;
1204	} );
1205
1206	// ID filter and find
1207	if ( support.getById ) {
1208		Expr.filter[ "ID" ] = function( id ) {
1209			var attrId = id.replace( runescape, funescape );
1210			return function( elem ) {
1211				return elem.getAttribute( "id" ) === attrId;
1212			};
1213		};
1214		Expr.find[ "ID" ] = function( id, context ) {
1215			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
1216				var elem = context.getElementById( id );
1217				return elem ? [ elem ] : [];
1218			}
1219		};
1220	} else {
1221		Expr.filter[ "ID" ] =  function( id ) {
1222			var attrId = id.replace( runescape, funescape );
1223			return function( elem ) {
1224				var node = typeof elem.getAttributeNode !== "undefined" &&
1225					elem.getAttributeNode( "id" );
1226				return node && node.value === attrId;
1227			};
1228		};
1229
1230		// Support: IE 6 - 7 only
1231		// getElementById is not reliable as a find shortcut
1232		Expr.find[ "ID" ] = function( id, context ) {
1233			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
1234				var node, i, elems,
1235					elem = context.getElementById( id );
1236
1237				if ( elem ) {
1238
1239					// Verify the id attribute
1240					node = elem.getAttributeNode( "id" );
1241					if ( node && node.value === id ) {
1242						return [ elem ];
1243					}
1244
1245					// Fall back on getElementsByName
1246					elems = context.getElementsByName( id );
1247					i = 0;
1248					while ( ( elem = elems[ i++ ] ) ) {
1249						node = elem.getAttributeNode( "id" );
1250						if ( node && node.value === id ) {
1251							return [ elem ];
1252						}
1253					}
1254				}
1255
1256				return [];
1257			}
1258		};
1259	}
1260
1261	// Tag
1262	Expr.find[ "TAG" ] = support.getElementsByTagName ?
1263		function( tag, context ) {
1264			if ( typeof context.getElementsByTagName !== "undefined" ) {
1265				return context.getElementsByTagName( tag );
1266
1267			// DocumentFragment nodes don't have gEBTN
1268			} else if ( support.qsa ) {
1269				return context.querySelectorAll( tag );
1270			}
1271		} :
1272
1273		function( tag, context ) {
1274			var elem,
1275				tmp = [],
1276				i = 0,
1277
1278				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
1279				results = context.getElementsByTagName( tag );
1280
1281			// Filter out possible comments
1282			if ( tag === "*" ) {
1283				while ( ( elem = results[ i++ ] ) ) {
1284					if ( elem.nodeType === 1 ) {
1285						tmp.push( elem );
1286					}
1287				}
1288
1289				return tmp;
1290			}
1291			return results;
1292		};
1293
1294	// Class
1295	Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) {
1296		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
1297			return context.getElementsByClassName( className );
1298		}
1299	};
1300
1301	/* QSA/matchesSelector
1302	---------------------------------------------------------------------- */
1303
1304	// QSA and matchesSelector support
1305
1306	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
1307	rbuggyMatches = [];
1308
1309	// qSa(:focus) reports false when true (Chrome 21)
1310	// We allow this because of a bug in IE8/9 that throws an error
1311	// whenever `document.activeElement` is accessed on an iframe
1312	// So, we allow :focus to pass through QSA all the time to avoid the IE error
1313	// See https://bugs.jquery.com/ticket/13378
1314	rbuggyQSA = [];
1315
1316	if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) {
1317
1318		// Build QSA regex
1319		// Regex strategy adopted from Diego Perini
1320		assert( function( el ) {
1321
1322			var input;
1323
1324			// Select is set to empty string on purpose
1325			// This is to test IE's treatment of not explicitly
1326			// setting a boolean content attribute,
1327			// since its presence should be enough
1328			// https://bugs.jquery.com/ticket/12359
1329			docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
1330				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
1331				"<option selected=''></option></select>";
1332
1333			// Support: IE8, Opera 11-12.16
1334			// Nothing should be selected when empty strings follow ^= or $= or *=
1335			// The test attribute must be unknown in Opera but "safe" for WinRT
1336			// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
1337			if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) {
1338				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
1339			}
1340
1341			// Support: IE8
1342			// Boolean attributes and "value" are not treated correctly
1343			if ( !el.querySelectorAll( "[selected]" ).length ) {
1344				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
1345			}
1346
1347			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
1348			if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
1349				rbuggyQSA.push( "~=" );
1350			}
1351
1352			// Support: IE 11+, Edge 15 - 18+
1353			// IE 11/Edge don't find elements on a `[name='']` query in some cases.
1354			// Adding a temporary attribute to the document before the selection works
1355			// around the issue.
1356			// Interestingly, IE 10 & older don't seem to have the issue.
1357			input = document.createElement( "input" );
1358			input.setAttribute( "name", "" );
1359			el.appendChild( input );
1360			if ( !el.querySelectorAll( "[name='']" ).length ) {
1361				rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
1362					whitespace + "*(?:''|\"\")" );
1363			}
1364
1365			// Webkit/Opera - :checked should return selected option elements
1366			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
1367			// IE8 throws error here and will not see later tests
1368			if ( !el.querySelectorAll( ":checked" ).length ) {
1369				rbuggyQSA.push( ":checked" );
1370			}
1371
1372			// Support: Safari 8+, iOS 8+
1373			// https://bugs.webkit.org/show_bug.cgi?id=136851
1374			// In-page `selector#id sibling-combinator selector` fails
1375			if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
1376				rbuggyQSA.push( ".#.+[+~]" );
1377			}
1378
1379			// Support: Firefox <=3.6 - 5 only
1380			// Old Firefox doesn't throw on a badly-escaped identifier.
1381			el.querySelectorAll( "\\\f" );
1382			rbuggyQSA.push( "[\\r\\n\\f]" );
1383		} );
1384
1385		assert( function( el ) {
1386			el.innerHTML = "<a href='' disabled='disabled'></a>" +
1387				"<select disabled='disabled'><option/></select>";
1388
1389			// Support: Windows 8 Native Apps
1390			// The type and name attributes are restricted during .innerHTML assignment
1391			var input = document.createElement( "input" );
1392			input.setAttribute( "type", "hidden" );
1393			el.appendChild( input ).setAttribute( "name", "D" );
1394
1395			// Support: IE8
1396			// Enforce case-sensitivity of name attribute
1397			if ( el.querySelectorAll( "[name=d]" ).length ) {
1398				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
1399			}
1400
1401			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
1402			// IE8 throws error here and will not see later tests
1403			if ( el.querySelectorAll( ":enabled" ).length !== 2 ) {
1404				rbuggyQSA.push( ":enabled", ":disabled" );
1405			}
1406
1407			// Support: IE9-11+
1408			// IE's :disabled selector does not pick up the children of disabled fieldsets
1409			docElem.appendChild( el ).disabled = true;
1410			if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
1411				rbuggyQSA.push( ":enabled", ":disabled" );
1412			}
1413
1414			// Support: Opera 10 - 11 only
1415			// Opera 10-11 does not throw on post-comma invalid pseudos
1416			el.querySelectorAll( "*,:x" );
1417			rbuggyQSA.push( ",.*:" );
1418		} );
1419	}
1420
1421	if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches ||
1422		docElem.webkitMatchesSelector ||
1423		docElem.mozMatchesSelector ||
1424		docElem.oMatchesSelector ||
1425		docElem.msMatchesSelector ) ) ) ) {
1426
1427		assert( function( el ) {
1428
1429			// Check to see if it's possible to do matchesSelector
1430			// on a disconnected node (IE 9)
1431			support.disconnectedMatch = matches.call( el, "*" );
1432
1433			// This should fail with an exception
1434			// Gecko does not error, returns false instead
1435			matches.call( el, "[s!='']:x" );
1436			rbuggyMatches.push( "!=", pseudos );
1437		} );
1438	}
1439
1440	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
1441	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
1442
1443	/* Contains
1444	---------------------------------------------------------------------- */
1445	hasCompare = rnative.test( docElem.compareDocumentPosition );
1446
1447	// Element contains another
1448	// Purposefully self-exclusive
1449	// As in, an element does not contain itself
1450	contains = hasCompare || rnative.test( docElem.contains ) ?
1451		function( a, b ) {
1452			var adown = a.nodeType === 9 ? a.documentElement : a,
1453				bup = b && b.parentNode;
1454			return a === bup || !!( bup && bup.nodeType === 1 && (
1455				adown.contains ?
1456					adown.contains( bup ) :
1457					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
1458			) );
1459		} :
1460		function( a, b ) {
1461			if ( b ) {
1462				while ( ( b = b.parentNode ) ) {
1463					if ( b === a ) {
1464						return true;
1465					}
1466				}
1467			}
1468			return false;
1469		};
1470
1471	/* Sorting
1472	---------------------------------------------------------------------- */
1473
1474	// Document order sorting
1475	sortOrder = hasCompare ?
1476	function( a, b ) {
1477
1478		// Flag for duplicate removal
1479		if ( a === b ) {
1480			hasDuplicate = true;
1481			return 0;
1482		}
1483
1484		// Sort on method existence if only one input has compareDocumentPosition
1485		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
1486		if ( compare ) {
1487			return compare;
1488		}
1489
1490		// Calculate position if both inputs belong to the same document
1491		// Support: IE 11+, Edge 17 - 18+
1492		// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1493		// two documents; shallow comparisons work.
1494		// eslint-disable-next-line eqeqeq
1495		compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
1496			a.compareDocumentPosition( b ) :
1497
1498			// Otherwise we know they are disconnected
1499			1;
1500
1501		// Disconnected nodes
1502		if ( compare & 1 ||
1503			( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
1504
1505			// Choose the first element that is related to our preferred document
1506			// Support: IE 11+, Edge 17 - 18+
1507			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1508			// two documents; shallow comparisons work.
1509			// eslint-disable-next-line eqeqeq
1510			if ( a == document || a.ownerDocument == preferredDoc &&
1511				contains( preferredDoc, a ) ) {
1512				return -1;
1513			}
1514
1515			// Support: IE 11+, Edge 17 - 18+
1516			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1517			// two documents; shallow comparisons work.
1518			// eslint-disable-next-line eqeqeq
1519			if ( b == document || b.ownerDocument == preferredDoc &&
1520				contains( preferredDoc, b ) ) {
1521				return 1;
1522			}
1523
1524			// Maintain original order
1525			return sortInput ?
1526				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
1527				0;
1528		}
1529
1530		return compare & 4 ? -1 : 1;
1531	} :
1532	function( a, b ) {
1533
1534		// Exit early if the nodes are identical
1535		if ( a === b ) {
1536			hasDuplicate = true;
1537			return 0;
1538		}
1539
1540		var cur,
1541			i = 0,
1542			aup = a.parentNode,
1543			bup = b.parentNode,
1544			ap = [ a ],
1545			bp = [ b ];
1546
1547		// Parentless nodes are either documents or disconnected
1548		if ( !aup || !bup ) {
1549
1550			// Support: IE 11+, Edge 17 - 18+
1551			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1552			// two documents; shallow comparisons work.
1553			/* eslint-disable eqeqeq */
1554			return a == document ? -1 :
1555				b == document ? 1 :
1556				/* eslint-enable eqeqeq */
1557				aup ? -1 :
1558				bup ? 1 :
1559				sortInput ?
1560				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
1561				0;
1562
1563		// If the nodes are siblings, we can do a quick check
1564		} else if ( aup === bup ) {
1565			return siblingCheck( a, b );
1566		}
1567
1568		// Otherwise we need full lists of their ancestors for comparison
1569		cur = a;
1570		while ( ( cur = cur.parentNode ) ) {
1571			ap.unshift( cur );
1572		}
1573		cur = b;
1574		while ( ( cur = cur.parentNode ) ) {
1575			bp.unshift( cur );
1576		}
1577
1578		// Walk down the tree looking for a discrepancy
1579		while ( ap[ i ] === bp[ i ] ) {
1580			i++;
1581		}
1582
1583		return i ?
1584
1585			// Do a sibling check if the nodes have a common ancestor
1586			siblingCheck( ap[ i ], bp[ i ] ) :
1587
1588			// Otherwise nodes in our document sort first
1589			// Support: IE 11+, Edge 17 - 18+
1590			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1591			// two documents; shallow comparisons work.
1592			/* eslint-disable eqeqeq */
1593			ap[ i ] == preferredDoc ? -1 :
1594			bp[ i ] == preferredDoc ? 1 :
1595			/* eslint-enable eqeqeq */
1596			0;
1597	};
1598
1599	return document;
1600};
1601
1602Sizzle.matches = function( expr, elements ) {
1603	return Sizzle( expr, null, null, elements );
1604};
1605
1606Sizzle.matchesSelector = function( elem, expr ) {
1607	setDocument( elem );
1608
1609	if ( support.matchesSelector && documentIsHTML &&
1610		!nonnativeSelectorCache[ expr + " " ] &&
1611		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
1612		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
1613
1614		try {
1615			var ret = matches.call( elem, expr );
1616
1617			// IE 9's matchesSelector returns false on disconnected nodes
1618			if ( ret || support.disconnectedMatch ||
1619
1620				// As well, disconnected nodes are said to be in a document
1621				// fragment in IE 9
1622				elem.document && elem.document.nodeType !== 11 ) {
1623				return ret;
1624			}
1625		} catch ( e ) {
1626			nonnativeSelectorCache( expr, true );
1627		}
1628	}
1629
1630	return Sizzle( expr, document, null, [ elem ] ).length > 0;
1631};
1632
1633Sizzle.contains = function( context, elem ) {
1634
1635	// Set document vars if needed
1636	// Support: IE 11+, Edge 17 - 18+
1637	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1638	// two documents; shallow comparisons work.
1639	// eslint-disable-next-line eqeqeq
1640	if ( ( context.ownerDocument || context ) != document ) {
1641		setDocument( context );
1642	}
1643	return contains( context, elem );
1644};
1645
1646Sizzle.attr = function( elem, name ) {
1647
1648	// Set document vars if needed
1649	// Support: IE 11+, Edge 17 - 18+
1650	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
1651	// two documents; shallow comparisons work.
1652	// eslint-disable-next-line eqeqeq
1653	if ( ( elem.ownerDocument || elem ) != document ) {
1654		setDocument( elem );
1655	}
1656
1657	var fn = Expr.attrHandle[ name.toLowerCase() ],
1658
1659		// Don't get fooled by Object.prototype properties (jQuery #13807)
1660		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
1661			fn( elem, name, !documentIsHTML ) :
1662			undefined;
1663
1664	return val !== undefined ?
1665		val :
1666		support.attributes || !documentIsHTML ?
1667			elem.getAttribute( name ) :
1668			( val = elem.getAttributeNode( name ) ) && val.specified ?
1669				val.value :
1670				null;
1671};
1672
1673Sizzle.escape = function( sel ) {
1674	return ( sel + "" ).replace( rcssescape, fcssescape );
1675};
1676
1677Sizzle.error = function( msg ) {
1678	throw new Error( "Syntax error, unrecognized expression: " + msg );
1679};
1680
1681/**
1682 * Document sorting and removing duplicates
1683 * @param {ArrayLike} results
1684 */
1685Sizzle.uniqueSort = function( results ) {
1686	var elem,
1687		duplicates = [],
1688		j = 0,
1689		i = 0;
1690
1691	// Unless we *know* we can detect duplicates, assume their presence
1692	hasDuplicate = !support.detectDuplicates;
1693	sortInput = !support.sortStable && results.slice( 0 );
1694	results.sort( sortOrder );
1695
1696	if ( hasDuplicate ) {
1697		while ( ( elem = results[ i++ ] ) ) {
1698			if ( elem === results[ i ] ) {
1699				j = duplicates.push( i );
1700			}
1701		}
1702		while ( j-- ) {
1703			results.splice( duplicates[ j ], 1 );
1704		}
1705	}
1706
1707	// Clear input after sorting to release objects
1708	// See https://github.com/jquery/sizzle/pull/225
1709	sortInput = null;
1710
1711	return results;
1712};
1713
1714/**
1715 * Utility function for retrieving the text value of an array of DOM nodes
1716 * @param {Array|Element} elem
1717 */
1718getText = Sizzle.getText = function( elem ) {
1719	var node,
1720		ret = "",
1721		i = 0,
1722		nodeType = elem.nodeType;
1723
1724	if ( !nodeType ) {
1725
1726		// If no nodeType, this is expected to be an array
1727		while ( ( node = elem[ i++ ] ) ) {
1728
1729			// Do not traverse comment nodes
1730			ret += getText( node );
1731		}
1732	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
1733
1734		// Use textContent for elements
1735		// innerText usage removed for consistency of new lines (jQuery #11153)
1736		if ( typeof elem.textContent === "string" ) {
1737			return elem.textContent;
1738		} else {
1739
1740			// Traverse its children
1741			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
1742				ret += getText( elem );
1743			}
1744		}
1745	} else if ( nodeType === 3 || nodeType === 4 ) {
1746		return elem.nodeValue;
1747	}
1748
1749	// Do not include comment or processing instruction nodes
1750
1751	return ret;
1752};
1753
1754Expr = Sizzle.selectors = {
1755
1756	// Can be adjusted by the user
1757	cacheLength: 50,
1758
1759	createPseudo: markFunction,
1760
1761	match: matchExpr,
1762
1763	attrHandle: {},
1764
1765	find: {},
1766
1767	relative: {
1768		">": { dir: "parentNode", first: true },
1769		" ": { dir: "parentNode" },
1770		"+": { dir: "previousSibling", first: true },
1771		"~": { dir: "previousSibling" }
1772	},
1773
1774	preFilter: {
1775		"ATTR": function( match ) {
1776			match[ 1 ] = match[ 1 ].replace( runescape, funescape );
1777
1778			// Move the given value to match[3] whether quoted or unquoted
1779			match[ 3 ] = ( match[ 3 ] || match[ 4 ] ||
1780				match[ 5 ] || "" ).replace( runescape, funescape );
1781
1782			if ( match[ 2 ] === "~=" ) {
1783				match[ 3 ] = " " + match[ 3 ] + " ";
1784			}
1785
1786			return match.slice( 0, 4 );
1787		},
1788
1789		"CHILD": function( match ) {
1790
1791			/* matches from matchExpr["CHILD"]
1792				1 type (only|nth|...)
1793				2 what (child|of-type)
1794				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
1795				4 xn-component of xn+y argument ([+-]?\d*n|)
1796				5 sign of xn-component
1797				6 x of xn-component
1798				7 sign of y-component
1799				8 y of y-component
1800			*/
1801			match[ 1 ] = match[ 1 ].toLowerCase();
1802
1803			if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
1804
1805				// nth-* requires argument
1806				if ( !match[ 3 ] ) {
1807					Sizzle.error( match[ 0 ] );
1808				}
1809
1810				// numeric x and y parameters for Expr.filter.CHILD
1811				// remember that false/true cast respectively to 0/1
1812				match[ 4 ] = +( match[ 4 ] ?
1813					match[ 5 ] + ( match[ 6 ] || 1 ) :
1814					2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) );
1815				match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
1816
1817				// other types prohibit arguments
1818			} else if ( match[ 3 ] ) {
1819				Sizzle.error( match[ 0 ] );
1820			}
1821
1822			return match;
1823		},
1824
1825		"PSEUDO": function( match ) {
1826			var excess,
1827				unquoted = !match[ 6 ] && match[ 2 ];
1828
1829			if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) {
1830				return null;
1831			}
1832
1833			// Accept quoted arguments as-is
1834			if ( match[ 3 ] ) {
1835				match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
1836
1837			// Strip excess characters from unquoted arguments
1838			} else if ( unquoted && rpseudo.test( unquoted ) &&
1839
1840				// Get excess from tokenize (recursively)
1841				( excess = tokenize( unquoted, true ) ) &&
1842
1843				// advance to the next closing parenthesis
1844				( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
1845
1846				// excess is a negative index
1847				match[ 0 ] = match[ 0 ].slice( 0, excess );
1848				match[ 2 ] = unquoted.slice( 0, excess );
1849			}
1850
1851			// Return only captures needed by the pseudo filter method (type and argument)
1852			return match.slice( 0, 3 );
1853		}
1854	},
1855
1856	filter: {
1857
1858		"TAG": function( nodeNameSelector ) {
1859			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
1860			return nodeNameSelector === "*" ?
1861				function() {
1862					return true;
1863				} :
1864				function( elem ) {
1865					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
1866				};
1867		},
1868
1869		"CLASS": function( className ) {
1870			var pattern = classCache[ className + " " ];
1871
1872			return pattern ||
1873				( pattern = new RegExp( "(^|" + whitespace +
1874					")" + className + "(" + whitespace + "|$)" ) ) && classCache(
1875						className, function( elem ) {
1876							return pattern.test(
1877								typeof elem.className === "string" && elem.className ||
1878								typeof elem.getAttribute !== "undefined" &&
1879									elem.getAttribute( "class" ) ||
1880								""
1881							);
1882				} );
1883		},
1884
1885		"ATTR": function( name, operator, check ) {
1886			return function( elem ) {
1887				var result = Sizzle.attr( elem, name );
1888
1889				if ( result == null ) {
1890					return operator === "!=";
1891				}
1892				if ( !operator ) {
1893					return true;
1894				}
1895
1896				result += "";
1897
1898				/* eslint-disable max-len */
1899
1900				return operator === "=" ? result === check :
1901					operator === "!=" ? result !== check :
1902					operator === "^=" ? check && result.indexOf( check ) === 0 :
1903					operator === "*=" ? check && result.indexOf( check ) > -1 :
1904					operator === "$=" ? check && result.slice( -check.length ) === check :
1905					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
1906					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
1907					false;
1908				/* eslint-enable max-len */
1909
1910			};
1911		},
1912
1913		"CHILD": function( type, what, _argument, first, last ) {
1914			var simple = type.slice( 0, 3 ) !== "nth",
1915				forward = type.slice( -4 ) !== "last",
1916				ofType = what === "of-type";
1917
1918			return first === 1 && last === 0 ?
1919
1920				// Shortcut for :nth-*(n)
1921				function( elem ) {
1922					return !!elem.parentNode;
1923				} :
1924
1925				function( elem, _context, xml ) {
1926					var cache, uniqueCache, outerCache, node, nodeIndex, start,
1927						dir = simple !== forward ? "nextSibling" : "previousSibling",
1928						parent = elem.parentNode,
1929						name = ofType && elem.nodeName.toLowerCase(),
1930						useCache = !xml && !ofType,
1931						diff = false;
1932
1933					if ( parent ) {
1934
1935						// :(first|last|only)-(child|of-type)
1936						if ( simple ) {
1937							while ( dir ) {
1938								node = elem;
1939								while ( ( node = node[ dir ] ) ) {
1940									if ( ofType ?
1941										node.nodeName.toLowerCase() === name :
1942										node.nodeType === 1 ) {
1943
1944										return false;
1945									}
1946								}
1947
1948								// Reverse direction for :only-* (if we haven't yet done so)
1949								start = dir = type === "only" && !start && "nextSibling";
1950							}
1951							return true;
1952						}
1953
1954						start = [ forward ? parent.firstChild : parent.lastChild ];
1955
1956						// non-xml :nth-child(...) stores cache data on `parent`
1957						if ( forward && useCache ) {
1958
1959							// Seek `elem` from a previously-cached index
1960
1961							// ...in a gzip-friendly way
1962							node = parent;
1963							outerCache = node[ expando ] || ( node[ expando ] = {} );
1964
1965							// Support: IE <9 only
1966							// Defend against cloned attroperties (jQuery gh-1709)
1967							uniqueCache = outerCache[ node.uniqueID ] ||
1968								( outerCache[ node.uniqueID ] = {} );
1969
1970							cache = uniqueCache[ type ] || [];
1971							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
1972							diff = nodeIndex && cache[ 2 ];
1973							node = nodeIndex && parent.childNodes[ nodeIndex ];
1974
1975							while ( ( node = ++nodeIndex && node && node[ dir ] ||
1976
1977								// Fallback to seeking `elem` from the start
1978								( diff = nodeIndex = 0 ) || start.pop() ) ) {
1979
1980								// When found, cache indexes on `parent` and break
1981								if ( node.nodeType === 1 && ++diff && node === elem ) {
1982									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
1983									break;
1984								}
1985							}
1986
1987						} else {
1988
1989							// Use previously-cached element index if available
1990							if ( useCache ) {
1991
1992								// ...in a gzip-friendly way
1993								node = elem;
1994								outerCache = node[ expando ] || ( node[ expando ] = {} );
1995
1996								// Support: IE <9 only
1997								// Defend against cloned attroperties (jQuery gh-1709)
1998								uniqueCache = outerCache[ node.uniqueID ] ||
1999									( outerCache[ node.uniqueID ] = {} );
2000
2001								cache = uniqueCache[ type ] || [];
2002								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
2003								diff = nodeIndex;
2004							}
2005
2006							// xml :nth-child(...)
2007							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
2008							if ( diff === false ) {
2009
2010								// Use the same loop as above to seek `elem` from the start
2011								while ( ( node = ++nodeIndex && node && node[ dir ] ||
2012									( diff = nodeIndex = 0 ) || start.pop() ) ) {
2013
2014									if ( ( ofType ?
2015										node.nodeName.toLowerCase() === name :
2016										node.nodeType === 1 ) &&
2017										++diff ) {
2018
2019										// Cache the index of each encountered element
2020										if ( useCache ) {
2021											outerCache = node[ expando ] ||
2022												( node[ expando ] = {} );
2023
2024											// Support: IE <9 only
2025											// Defend against cloned attroperties (jQuery gh-1709)
2026											uniqueCache = outerCache[ node.uniqueID ] ||
2027												( outerCache[ node.uniqueID ] = {} );
2028
2029											uniqueCache[ type ] = [ dirruns, diff ];
2030										}
2031
2032										if ( node === elem ) {
2033											break;
2034										}
2035									}
2036								}
2037							}
2038						}
2039
2040						// Incorporate the offset, then check against cycle size
2041						diff -= last;
2042						return diff === first || ( diff % first === 0 && diff / first >= 0 );
2043					}
2044				};
2045		},
2046
2047		"PSEUDO": function( pseudo, argument ) {
2048
2049			// pseudo-class names are case-insensitive
2050			// http://www.w3.org/TR/selectors/#pseudo-classes
2051			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
2052			// Remember that setFilters inherits from pseudos
2053			var args,
2054				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
2055					Sizzle.error( "unsupported pseudo: " + pseudo );
2056
2057			// The user may use createPseudo to indicate that
2058			// arguments are needed to create the filter function
2059			// just as Sizzle does
2060			if ( fn[ expando ] ) {
2061				return fn( argument );
2062			}
2063
2064			// But maintain support for old signatures
2065			if ( fn.length > 1 ) {
2066				args = [ pseudo, pseudo, "", argument ];
2067				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
2068					markFunction( function( seed, matches ) {
2069						var idx,
2070							matched = fn( seed, argument ),
2071							i = matched.length;
2072						while ( i-- ) {
2073							idx = indexOf( seed, matched[ i ] );
2074							seed[ idx ] = !( matches[ idx ] = matched[ i ] );
2075						}
2076					} ) :
2077					function( elem ) {
2078						return fn( elem, 0, args );
2079					};
2080			}
2081
2082			return fn;
2083		}
2084	},
2085
2086	pseudos: {
2087
2088		// Potentially complex pseudos
2089		"not": markFunction( function( selector ) {
2090
2091			// Trim the selector passed to compile
2092			// to avoid treating leading and trailing
2093			// spaces as combinators
2094			var input = [],
2095				results = [],
2096				matcher = compile( selector.replace( rtrim, "$1" ) );
2097
2098			return matcher[ expando ] ?
2099				markFunction( function( seed, matches, _context, xml ) {
2100					var elem,
2101						unmatched = matcher( seed, null, xml, [] ),
2102						i = seed.length;
2103
2104					// Match elements unmatched by `matcher`
2105					while ( i-- ) {
2106						if ( ( elem = unmatched[ i ] ) ) {
2107							seed[ i ] = !( matches[ i ] = elem );
2108						}
2109					}
2110				} ) :
2111				function( elem, _context, xml ) {
2112					input[ 0 ] = elem;
2113					matcher( input, null, xml, results );
2114
2115					// Don't keep the element (issue #299)
2116					input[ 0 ] = null;
2117					return !results.pop();
2118				};
2119		} ),
2120
2121		"has": markFunction( function( selector ) {
2122			return function( elem ) {
2123				return Sizzle( selector, elem ).length > 0;
2124			};
2125		} ),
2126
2127		"contains": markFunction( function( text ) {
2128			text = text.replace( runescape, funescape );
2129			return function( elem ) {
2130				return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
2131			};
2132		} ),
2133
2134		// "Whether an element is represented by a :lang() selector
2135		// is based solely on the element's language value
2136		// being equal to the identifier C,
2137		// or beginning with the identifier C immediately followed by "-".
2138		// The matching of C against the element's language value is performed case-insensitively.
2139		// The identifier C does not have to be a valid language name."
2140		// http://www.w3.org/TR/selectors/#lang-pseudo
2141		"lang": markFunction( function( lang ) {
2142
2143			// lang value must be a valid identifier
2144			if ( !ridentifier.test( lang || "" ) ) {
2145				Sizzle.error( "unsupported lang: " + lang );
2146			}
2147			lang = lang.replace( runescape, funescape ).toLowerCase();
2148			return function( elem ) {
2149				var elemLang;
2150				do {
2151					if ( ( elemLang = documentIsHTML ?
2152						elem.lang :
2153						elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
2154
2155						elemLang = elemLang.toLowerCase();
2156						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
2157					}
2158				} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
2159				return false;
2160			};
2161		} ),
2162
2163		// Miscellaneous
2164		"target": function( elem ) {
2165			var hash = window.location && window.location.hash;
2166			return hash && hash.slice( 1 ) === elem.id;
2167		},
2168
2169		"root": function( elem ) {
2170			return elem === docElem;
2171		},
2172
2173		"focus": function( elem ) {
2174			return elem === document.activeElement &&
2175				( !document.hasFocus || document.hasFocus() ) &&
2176				!!( elem.type || elem.href || ~elem.tabIndex );
2177		},
2178
2179		// Boolean properties
2180		"enabled": createDisabledPseudo( false ),
2181		"disabled": createDisabledPseudo( true ),
2182
2183		"checked": function( elem ) {
2184
2185			// In CSS3, :checked should return both checked and selected elements
2186			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
2187			var nodeName = elem.nodeName.toLowerCase();
2188			return ( nodeName === "input" && !!elem.checked ) ||
2189				( nodeName === "option" && !!elem.selected );
2190		},
2191
2192		"selected": function( elem ) {
2193
2194			// Accessing this property makes selected-by-default
2195			// options in Safari work properly
2196			if ( elem.parentNode ) {
2197				// eslint-disable-next-line no-unused-expressions
2198				elem.parentNode.selectedIndex;
2199			}
2200
2201			return elem.selected === true;
2202		},
2203
2204		// Contents
2205		"empty": function( elem ) {
2206
2207			// http://www.w3.org/TR/selectors/#empty-pseudo
2208			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
2209			//   but not by others (comment: 8; processing instruction: 7; etc.)
2210			// nodeType < 6 works because attributes (2) do not appear as children
2211			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
2212				if ( elem.nodeType < 6 ) {
2213					return false;
2214				}
2215			}
2216			return true;
2217		},
2218
2219		"parent": function( elem ) {
2220			return !Expr.pseudos[ "empty" ]( elem );
2221		},
2222
2223		// Element/input types
2224		"header": function( elem ) {
2225			return rheader.test( elem.nodeName );
2226		},
2227
2228		"input": function( elem ) {
2229			return rinputs.test( elem.nodeName );
2230		},
2231
2232		"button": function( elem ) {
2233			var name = elem.nodeName.toLowerCase();
2234			return name === "input" && elem.type === "button" || name === "button";
2235		},
2236
2237		"text": function( elem ) {
2238			var attr;
2239			return elem.nodeName.toLowerCase() === "input" &&
2240				elem.type === "text" &&
2241
2242				// Support: IE<8
2243				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
2244				( ( attr = elem.getAttribute( "type" ) ) == null ||
2245					attr.toLowerCase() === "text" );
2246		},
2247
2248		// Position-in-collection
2249		"first": createPositionalPseudo( function() {
2250			return [ 0 ];
2251		} ),
2252
2253		"last": createPositionalPseudo( function( _matchIndexes, length ) {
2254			return [ length - 1 ];
2255		} ),
2256
2257		"eq": createPositionalPseudo( function( _matchIndexes, length, argument ) {
2258			return [ argument < 0 ? argument + length : argument ];
2259		} ),
2260
2261		"even": createPositionalPseudo( function( matchIndexes, length ) {
2262			var i = 0;
2263			for ( ; i < length; i += 2 ) {
2264				matchIndexes.push( i );
2265			}
2266			return matchIndexes;
2267		} ),
2268
2269		"odd": createPositionalPseudo( function( matchIndexes, length ) {
2270			var i = 1;
2271			for ( ; i < length; i += 2 ) {
2272				matchIndexes.push( i );
2273			}
2274			return matchIndexes;
2275		} ),
2276
2277		"lt": createPositionalPseudo( function( matchIndexes, length, argument ) {
2278			var i = argument < 0 ?
2279				argument + length :
2280				argument > length ?
2281					length :
2282					argument;
2283			for ( ; --i >= 0; ) {
2284				matchIndexes.push( i );
2285			}
2286			return matchIndexes;
2287		} ),
2288
2289		"gt": createPositionalPseudo( function( matchIndexes, length, argument ) {
2290			var i = argument < 0 ? argument + length : argument;
2291			for ( ; ++i < length; ) {
2292				matchIndexes.push( i );
2293			}
2294			return matchIndexes;
2295		} )
2296	}
2297};
2298
2299Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ];
2300
2301// Add button/input type pseudos
2302for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
2303	Expr.pseudos[ i ] = createInputPseudo( i );
2304}
2305for ( i in { submit: true, reset: true } ) {
2306	Expr.pseudos[ i ] = createButtonPseudo( i );
2307}
2308
2309// Easy API for creating new setFilters
2310function setFilters() {}
2311setFilters.prototype = Expr.filters = Expr.pseudos;
2312Expr.setFilters = new setFilters();
2313
2314tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
2315	var matched, match, tokens, type,
2316		soFar, groups, preFilters,
2317		cached = tokenCache[ selector + " " ];
2318
2319	if ( cached ) {
2320		return parseOnly ? 0 : cached.slice( 0 );
2321	}
2322
2323	soFar = selector;
2324	groups = [];
2325	preFilters = Expr.preFilter;
2326
2327	while ( soFar ) {
2328
2329		// Comma and first run
2330		if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
2331			if ( match ) {
2332
2333				// Don't consume trailing commas as valid
2334				soFar = soFar.slice( match[ 0 ].length ) || soFar;
2335			}
2336			groups.push( ( tokens = [] ) );
2337		}
2338
2339		matched = false;
2340
2341		// Combinators
2342		if ( ( match = rcombinators.exec( soFar ) ) ) {
2343			matched = match.shift();
2344			tokens.push( {
2345				value: matched,
2346
2347				// Cast descendant combinators to space
2348				type: match[ 0 ].replace( rtrim, " " )
2349			} );
2350			soFar = soFar.slice( matched.length );
2351		}
2352
2353		// Filters
2354		for ( type in Expr.filter ) {
2355			if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
2356				( match = preFilters[ type ]( match ) ) ) ) {
2357				matched = match.shift();
2358				tokens.push( {
2359					value: matched,
2360					type: type,
2361					matches: match
2362				} );
2363				soFar = soFar.slice( matched.length );
2364			}
2365		}
2366
2367		if ( !matched ) {
2368			break;
2369		}
2370	}
2371
2372	// Return the length of the invalid excess
2373	// if we're just parsing
2374	// Otherwise, throw an error or return tokens
2375	return parseOnly ?
2376		soFar.length :
2377		soFar ?
2378			Sizzle.error( selector ) :
2379
2380			// Cache the tokens
2381			tokenCache( selector, groups ).slice( 0 );
2382};
2383
2384function toSelector( tokens ) {
2385	var i = 0,
2386		len = tokens.length,
2387		selector = "";
2388	for ( ; i < len; i++ ) {
2389		selector += tokens[ i ].value;
2390	}
2391	return selector;
2392}
2393
2394function addCombinator( matcher, combinator, base ) {
2395	var dir = combinator.dir,
2396		skip = combinator.next,
2397		key = skip || dir,
2398		checkNonElements = base && key === "parentNode",
2399		doneName = done++;
2400
2401	return combinator.first ?
2402
2403		// Check against closest ancestor/preceding element
2404		function( elem, context, xml ) {
2405			while ( ( elem = elem[ dir ] ) ) {
2406				if ( elem.nodeType === 1 || checkNonElements ) {
2407					return matcher( elem, context, xml );
2408				}
2409			}
2410			return false;
2411		} :
2412
2413		// Check against all ancestor/preceding elements
2414		function( elem, context, xml ) {
2415			var oldCache, uniqueCache, outerCache,
2416				newCache = [ dirruns, doneName ];
2417
2418			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
2419			if ( xml ) {
2420				while ( ( elem = elem[ dir ] ) ) {
2421					if ( elem.nodeType === 1 || checkNonElements ) {
2422						if ( matcher( elem, context, xml ) ) {
2423							return true;
2424						}
2425					}
2426				}
2427			} else {
2428				while ( ( elem = elem[ dir ] ) ) {
2429					if ( elem.nodeType === 1 || checkNonElements ) {
2430						outerCache = elem[ expando ] || ( elem[ expando ] = {} );
2431
2432						// Support: IE <9 only
2433						// Defend against cloned attroperties (jQuery gh-1709)
2434						uniqueCache = outerCache[ elem.uniqueID ] ||
2435							( outerCache[ elem.uniqueID ] = {} );
2436
2437						if ( skip && skip === elem.nodeName.toLowerCase() ) {
2438							elem = elem[ dir ] || elem;
2439						} else if ( ( oldCache = uniqueCache[ key ] ) &&
2440							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
2441
2442							// Assign to newCache so results back-propagate to previous elements
2443							return ( newCache[ 2 ] = oldCache[ 2 ] );
2444						} else {
2445
2446							// Reuse newcache so results back-propagate to previous elements
2447							uniqueCache[ key ] = newCache;
2448
2449							// A match means we're done; a fail means we have to keep checking
2450							if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
2451								return true;
2452							}
2453						}
2454					}
2455				}
2456			}
2457			return false;
2458		};
2459}
2460
2461function elementMatcher( matchers ) {
2462	return matchers.length > 1 ?
2463		function( elem, context, xml ) {
2464			var i = matchers.length;
2465			while ( i-- ) {
2466				if ( !matchers[ i ]( elem, context, xml ) ) {
2467					return false;
2468				}
2469			}
2470			return true;
2471		} :
2472		matchers[ 0 ];
2473}
2474
2475function multipleContexts( selector, contexts, results ) {
2476	var i = 0,
2477		len = contexts.length;
2478	for ( ; i < len; i++ ) {
2479		Sizzle( selector, contexts[ i ], results );
2480	}
2481	return results;
2482}
2483
2484function condense( unmatched, map, filter, context, xml ) {
2485	var elem,
2486		newUnmatched = [],
2487		i = 0,
2488		len = unmatched.length,
2489		mapped = map != null;
2490
2491	for ( ; i < len; i++ ) {
2492		if ( ( elem = unmatched[ i ] ) ) {
2493			if ( !filter || filter( elem, context, xml ) ) {
2494				newUnmatched.push( elem );
2495				if ( mapped ) {
2496					map.push( i );
2497				}
2498			}
2499		}
2500	}
2501
2502	return newUnmatched;
2503}
2504
2505function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
2506	if ( postFilter && !postFilter[ expando ] ) {
2507		postFilter = setMatcher( postFilter );
2508	}
2509	if ( postFinder && !postFinder[ expando ] ) {
2510		postFinder = setMatcher( postFinder, postSelector );
2511	}
2512	return markFunction( function( seed, results, context, xml ) {
2513		var temp, i, elem,
2514			preMap = [],
2515			postMap = [],
2516			preexisting = results.length,
2517
2518			// Get initial elements from seed or context
2519			elems = seed || multipleContexts(
2520				selector || "*",
2521				context.nodeType ? [ context ] : context,
2522				[]
2523			),
2524
2525			// Prefilter to get matcher input, preserving a map for seed-results synchronization
2526			matcherIn = preFilter && ( seed || !selector ) ?
2527				condense( elems, preMap, preFilter, context, xml ) :
2528				elems,
2529
2530			matcherOut = matcher ?
2531
2532				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
2533				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
2534
2535					// ...intermediate processing is necessary
2536					[] :
2537
2538					// ...otherwise use results directly
2539					results :
2540				matcherIn;
2541
2542		// Find primary matches
2543		if ( matcher ) {
2544			matcher( matcherIn, matcherOut, context, xml );
2545		}
2546
2547		// Apply postFilter
2548		if ( postFilter ) {
2549			temp = condense( matcherOut, postMap );
2550			postFilter( temp, [], context, xml );
2551
2552			// Un-match failing elements by moving them back to matcherIn
2553			i = temp.length;
2554			while ( i-- ) {
2555				if ( ( elem = temp[ i ] ) ) {
2556					matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
2557				}
2558			}
2559		}
2560
2561		if ( seed ) {
2562			if ( postFinder || preFilter ) {
2563				if ( postFinder ) {
2564
2565					// Get the final matcherOut by condensing this intermediate into postFinder contexts
2566					temp = [];
2567					i = matcherOut.length;
2568					while ( i-- ) {
2569						if ( ( elem = matcherOut[ i ] ) ) {
2570
2571							// Restore matcherIn since elem is not yet a final match
2572							temp.push( ( matcherIn[ i ] = elem ) );
2573						}
2574					}
2575					postFinder( null, ( matcherOut = [] ), temp, xml );
2576				}
2577
2578				// Move matched elements from seed to results to keep them synchronized
2579				i = matcherOut.length;
2580				while ( i-- ) {
2581					if ( ( elem = matcherOut[ i ] ) &&
2582						( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) {
2583
2584						seed[ temp ] = !( results[ temp ] = elem );
2585					}
2586				}
2587			}
2588
2589		// Add elements to results, through postFinder if defined
2590		} else {
2591			matcherOut = condense(
2592				matcherOut === results ?
2593					matcherOut.splice( preexisting, matcherOut.length ) :
2594					matcherOut
2595			);
2596			if ( postFinder ) {
2597				postFinder( null, results, matcherOut, xml );
2598			} else {
2599				push.apply( results, matcherOut );
2600			}
2601		}
2602	} );
2603}
2604
2605function matcherFromTokens( tokens ) {
2606	var checkContext, matcher, j,
2607		len = tokens.length,
2608		leadingRelative = Expr.relative[ tokens[ 0 ].type ],
2609		implicitRelative = leadingRelative || Expr.relative[ " " ],
2610		i = leadingRelative ? 1 : 0,
2611
2612		// The foundational matcher ensures that elements are reachable from top-level context(s)
2613		matchContext = addCombinator( function( elem ) {
2614			return elem === checkContext;
2615		}, implicitRelative, true ),
2616		matchAnyContext = addCombinator( function( elem ) {
2617			return indexOf( checkContext, elem ) > -1;
2618		}, implicitRelative, true ),
2619		matchers = [ function( elem, context, xml ) {
2620			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
2621				( checkContext = context ).nodeType ?
2622					matchContext( elem, context, xml ) :
2623					matchAnyContext( elem, context, xml ) );
2624
2625			// Avoid hanging onto element (issue #299)
2626			checkContext = null;
2627			return ret;
2628		} ];
2629
2630	for ( ; i < len; i++ ) {
2631		if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
2632			matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
2633		} else {
2634			matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
2635
2636			// Return special upon seeing a positional matcher
2637			if ( matcher[ expando ] ) {
2638
2639				// Find the next relative operator (if any) for proper handling
2640				j = ++i;
2641				for ( ; j < len; j++ ) {
2642					if ( Expr.relative[ tokens[ j ].type ] ) {
2643						break;
2644					}
2645				}
2646				return setMatcher(
2647					i > 1 && elementMatcher( matchers ),
2648					i > 1 && toSelector(
2649
2650					// If the preceding token was a descendant combinator, insert an implicit any-element `*`
2651					tokens
2652						.slice( 0, i - 1 )
2653						.concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
2654					).replace( rtrim, "$1" ),
2655					matcher,
2656					i < j && matcherFromTokens( tokens.slice( i, j ) ),
2657					j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
2658					j < len && toSelector( tokens )
2659				);
2660			}
2661			matchers.push( matcher );
2662		}
2663	}
2664
2665	return elementMatcher( matchers );
2666}
2667
2668function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
2669	var bySet = setMatchers.length > 0,
2670		byElement = elementMatchers.length > 0,
2671		superMatcher = function( seed, context, xml, results, outermost ) {
2672			var elem, j, matcher,
2673				matchedCount = 0,
2674				i = "0",
2675				unmatched = seed && [],
2676				setMatched = [],
2677				contextBackup = outermostContext,
2678
2679				// We must always have either seed elements or outermost context
2680				elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ),
2681
2682				// Use integer dirruns iff this is the outermost matcher
2683				dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
2684				len = elems.length;
2685
2686			if ( outermost ) {
2687
2688				// Support: IE 11+, Edge 17 - 18+
2689				// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
2690				// two documents; shallow comparisons work.
2691				// eslint-disable-next-line eqeqeq
2692				outermostContext = context == document || context || outermost;
2693			}
2694
2695			// Add elements passing elementMatchers directly to results
2696			// Support: IE<9, Safari
2697			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
2698			for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
2699				if ( byElement && elem ) {
2700					j = 0;
2701
2702					// Support: IE 11+, Edge 17 - 18+
2703					// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
2704					// two documents; shallow comparisons work.
2705					// eslint-disable-next-line eqeqeq
2706					if ( !context && elem.ownerDocument != document ) {
2707						setDocument( elem );
2708						xml = !documentIsHTML;
2709					}
2710					while ( ( matcher = elementMatchers[ j++ ] ) ) {
2711						if ( matcher( elem, context || document, xml ) ) {
2712							results.push( elem );
2713							break;
2714						}
2715					}
2716					if ( outermost ) {
2717						dirruns = dirrunsUnique;
2718					}
2719				}
2720
2721				// Track unmatched elements for set filters
2722				if ( bySet ) {
2723
2724					// They will have gone through all possible matchers
2725					if ( ( elem = !matcher && elem ) ) {
2726						matchedCount--;
2727					}
2728
2729					// Lengthen the array for every element, matched or not
2730					if ( seed ) {
2731						unmatched.push( elem );
2732					}
2733				}
2734			}
2735
2736			// `i` is now the count of elements visited above, and adding it to `matchedCount`
2737			// makes the latter nonnegative.
2738			matchedCount += i;
2739
2740			// Apply set filters to unmatched elements
2741			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
2742			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
2743			// no element matchers and no seed.
2744			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
2745			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
2746			// numerically zero.
2747			if ( bySet && i !== matchedCount ) {
2748				j = 0;
2749				while ( ( matcher = setMatchers[ j++ ] ) ) {
2750					matcher( unmatched, setMatched, context, xml );
2751				}
2752
2753				if ( seed ) {
2754
2755					// Reintegrate element matches to eliminate the need for sorting
2756					if ( matchedCount > 0 ) {
2757						while ( i-- ) {
2758							if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
2759								setMatched[ i ] = pop.call( results );
2760							}
2761						}
2762					}
2763
2764					// Discard index placeholder values to get only actual matches
2765					setMatched = condense( setMatched );
2766				}
2767
2768				// Add matches to results
2769				push.apply( results, setMatched );
2770
2771				// Seedless set matches succeeding multiple successful matchers stipulate sorting
2772				if ( outermost && !seed && setMatched.length > 0 &&
2773					( matchedCount + setMatchers.length ) > 1 ) {
2774
2775					Sizzle.uniqueSort( results );
2776				}
2777			}
2778
2779			// Override manipulation of globals by nested matchers
2780			if ( outermost ) {
2781				dirruns = dirrunsUnique;
2782				outermostContext = contextBackup;
2783			}
2784
2785			return unmatched;
2786		};
2787
2788	return bySet ?
2789		markFunction( superMatcher ) :
2790		superMatcher;
2791}
2792
2793compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
2794	var i,
2795		setMatchers = [],
2796		elementMatchers = [],
2797		cached = compilerCache[ selector + " " ];
2798
2799	if ( !cached ) {
2800
2801		// Generate a function of recursive functions that can be used to check each element
2802		if ( !match ) {
2803			match = tokenize( selector );
2804		}
2805		i = match.length;
2806		while ( i-- ) {
2807			cached = matcherFromTokens( match[ i ] );
2808			if ( cached[ expando ] ) {
2809				setMatchers.push( cached );
2810			} else {
2811				elementMatchers.push( cached );
2812			}
2813		}
2814
2815		// Cache the compiled function
2816		cached = compilerCache(
2817			selector,
2818			matcherFromGroupMatchers( elementMatchers, setMatchers )
2819		);
2820
2821		// Save selector and tokenization
2822		cached.selector = selector;
2823	}
2824	return cached;
2825};
2826
2827/**
2828 * A low-level selection function that works with Sizzle's compiled
2829 *  selector functions
2830 * @param {String|Function} selector A selector or a pre-compiled
2831 *  selector function built with Sizzle.compile
2832 * @param {Element} context
2833 * @param {Array} [results]
2834 * @param {Array} [seed] A set of elements to match against
2835 */
2836select = Sizzle.select = function( selector, context, results, seed ) {
2837	var i, tokens, token, type, find,
2838		compiled = typeof selector === "function" && selector,
2839		match = !seed && tokenize( ( selector = compiled.selector || selector ) );
2840
2841	results = results || [];
2842
2843	// Try to minimize operations if there is only one selector in the list and no seed
2844	// (the latter of which guarantees us context)
2845	if ( match.length === 1 ) {
2846
2847		// Reduce context if the leading compound selector is an ID
2848		tokens = match[ 0 ] = match[ 0 ].slice( 0 );
2849		if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
2850			context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
2851
2852			context = ( Expr.find[ "ID" ]( token.matches[ 0 ]
2853				.replace( runescape, funescape ), context ) || [] )[ 0 ];
2854			if ( !context ) {
2855				return results;
2856
2857			// Precompiled matchers will still verify ancestry, so step up a level
2858			} else if ( compiled ) {
2859				context = context.parentNode;
2860			}
2861
2862			selector = selector.slice( tokens.shift().value.length );
2863		}
2864
2865		// Fetch a seed set for right-to-left matching
2866		i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length;
2867		while ( i-- ) {
2868			token = tokens[ i ];
2869
2870			// Abort if we hit a combinator
2871			if ( Expr.relative[ ( type = token.type ) ] ) {
2872				break;
2873			}
2874			if ( ( find = Expr.find[ type ] ) ) {
2875
2876				// Search, expanding context for leading sibling combinators
2877				if ( ( seed = find(
2878					token.matches[ 0 ].replace( runescape, funescape ),
2879					rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) ||
2880						context
2881				) ) ) {
2882
2883					// If seed is empty or no tokens remain, we can return early
2884					tokens.splice( i, 1 );
2885					selector = seed.length && toSelector( tokens );
2886					if ( !selector ) {
2887						push.apply( results, seed );
2888						return results;
2889					}
2890
2891					break;
2892				}
2893			}
2894		}
2895	}
2896
2897	// Compile and execute a filtering function if one is not provided
2898	// Provide `match` to avoid retokenization if we modified the selector above
2899	( compiled || compile( selector, match ) )(
2900		seed,
2901		context,
2902		!documentIsHTML,
2903		results,
2904		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
2905	);
2906	return results;
2907};
2908
2909// One-time assignments
2910
2911// Sort stability
2912support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
2913
2914// Support: Chrome 14-35+
2915// Always assume duplicates if they aren't passed to the comparison function
2916support.detectDuplicates = !!hasDuplicate;
2917
2918// Initialize against the default document
2919setDocument();
2920
2921// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
2922// Detached nodes confoundingly follow *each other*
2923support.sortDetached = assert( function( el ) {
2924
2925	// Should return 1, but returns 4 (following)
2926	return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
2927} );
2928
2929// Support: IE<8
2930// Prevent attribute/property "interpolation"
2931// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
2932if ( !assert( function( el ) {
2933	el.innerHTML = "<a href='#'></a>";
2934	return el.firstChild.getAttribute( "href" ) === "#";
2935} ) ) {
2936	addHandle( "type|href|height|width", function( elem, name, isXML ) {
2937		if ( !isXML ) {
2938			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
2939		}
2940	} );
2941}
2942
2943// Support: IE<9
2944// Use defaultValue in place of getAttribute("value")
2945if ( !support.attributes || !assert( function( el ) {
2946	el.innerHTML = "<input/>";
2947	el.firstChild.setAttribute( "value", "" );
2948	return el.firstChild.getAttribute( "value" ) === "";
2949} ) ) {
2950	addHandle( "value", function( elem, _name, isXML ) {
2951		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
2952			return elem.defaultValue;
2953		}
2954	} );
2955}
2956
2957// Support: IE<9
2958// Use getAttributeNode to fetch booleans when getAttribute lies
2959if ( !assert( function( el ) {
2960	return el.getAttribute( "disabled" ) == null;
2961} ) ) {
2962	addHandle( booleans, function( elem, name, isXML ) {
2963		var val;
2964		if ( !isXML ) {
2965			return elem[ name ] === true ? name.toLowerCase() :
2966				( val = elem.getAttributeNode( name ) ) && val.specified ?
2967					val.value :
2968					null;
2969		}
2970	} );
2971}
2972
2973return Sizzle;
2974
2975} )( window );
2976
2977
2978
2979jQuery.find = Sizzle;
2980jQuery.expr = Sizzle.selectors;
2981
2982// Deprecated
2983jQuery.expr[ ":" ] = jQuery.expr.pseudos;
2984jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
2985jQuery.text = Sizzle.getText;
2986jQuery.isXMLDoc = Sizzle.isXML;
2987jQuery.contains = Sizzle.contains;
2988jQuery.escapeSelector = Sizzle.escape;
2989
2990
2991
2992
2993var dir = function( elem, dir, until ) {
2994	var matched = [],
2995		truncate = until !== undefined;
2996
2997	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
2998		if ( elem.nodeType === 1 ) {
2999			if ( truncate && jQuery( elem ).is( until ) ) {
3000				break;
3001			}
3002			matched.push( elem );
3003		}
3004	}
3005	return matched;
3006};
3007
3008
3009var siblings = function( n, elem ) {
3010	var matched = [];
3011
3012	for ( ; n; n = n.nextSibling ) {
3013		if ( n.nodeType === 1 && n !== elem ) {
3014			matched.push( n );
3015		}
3016	}
3017
3018	return matched;
3019};
3020
3021
3022var rneedsContext = jQuery.expr.match.needsContext;
3023
3024
3025
3026function nodeName( elem, name ) {
3027
3028  return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
3029
3030};
vendor: 21,546 bytes, lines 3030-3855
3030
3031var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
3032
3033
3034
3035// Implement the identical functionality for filter and not
3036function winnow( elements, qualifier, not ) {
3037	if ( isFunction( qualifier ) ) {
3038		return jQuery.grep( elements, function( elem, i ) {
3039			return !!qualifier.call( elem, i, elem ) !== not;
3040		} );
3041	}
3042
3043	// Single element
3044	if ( qualifier.nodeType ) {
3045		return jQuery.grep( elements, function( elem ) {
3046			return ( elem === qualifier ) !== not;
3047		} );
3048	}
3049
3050	// Arraylike of elements (jQuery, arguments, Array)
3051	if ( typeof qualifier !== "string" ) {
3052		return jQuery.grep( elements, function( elem ) {
3053			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
3054		} );
3055	}
3056
3057	// Filtered directly for both simple and complex selectors
3058	return jQuery.filter( qualifier, elements, not );
3059}
3060
3061jQuery.filter = function( expr, elems, not ) {
3062	var elem = elems[ 0 ];
3063
3064	if ( not ) {
3065		expr = ":not(" + expr + ")";
3066	}
3067
3068	if ( elems.length === 1 && elem.nodeType === 1 ) {
3069		return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
3070	}
3071
3072	return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
3073		return elem.nodeType === 1;
3074	} ) );
3075};
3076
3077jQuery.fn.extend( {
3078	find: function( selector ) {
3079		var i, ret,
3080			len = this.length,
3081			self = this;
3082
3083		if ( typeof selector !== "string" ) {
3084			return this.pushStack( jQuery( selector ).filter( function() {
3085				for ( i = 0; i < len; i++ ) {
3086					if ( jQuery.contains( self[ i ], this ) ) {
3087						return true;
3088					}
3089				}
3090			} ) );
3091		}
3092
3093		ret = this.pushStack( [] );
3094
3095		for ( i = 0; i < len; i++ ) {
3096			jQuery.find( selector, self[ i ], ret );
3097		}
3098
3099		return len > 1 ? jQuery.uniqueSort( ret ) : ret;
3100	},
3101	filter: function( selector ) {
3102		return this.pushStack( winnow( this, selector || [], false ) );
3103	},
3104	not: function( selector ) {
3105		return this.pushStack( winnow( this, selector || [], true ) );
3106	},
3107	is: function( selector ) {
3108		return !!winnow(
3109			this,
3110
3111			// If this is a positional/relative selector, check membership in the returned set
3112			// so $("p:first").is("p:last") won't return true for a doc with two "p".
3113			typeof selector === "string" && rneedsContext.test( selector ) ?
3114				jQuery( selector ) :
3115				selector || [],
3116			false
3117		).length;
3118	}
3119} );
3120
3121
3122// Initialize a jQuery object
3123
3124
3125// A central reference to the root jQuery(document)
3126var rootjQuery,
3127
3128	// A simple way to check for HTML strings
3129	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
3130	// Strict HTML recognition (#11290: must start with <)
3131	// Shortcut simple #id case for speed
3132	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
3133
3134	init = jQuery.fn.init = function( selector, context, root ) {
3135		var match, elem;
3136
3137		// HANDLE: $(""), $(null), $(undefined), $(false)
3138		if ( !selector ) {
3139			return this;
3140		}
3141
3142		// Method init() accepts an alternate rootjQuery
3143		// so migrate can support jQuery.sub (gh-2101)
3144		root = root || rootjQuery;
3145
3146		// Handle HTML strings
3147		if ( typeof selector === "string" ) {
3148			if ( selector[ 0 ] === "<" &&
3149				selector[ selector.length - 1 ] === ">" &&
3150				selector.length >= 3 ) {
3151
3152				// Assume that strings that start and end with <> are HTML and skip the regex check
3153				match = [ null, selector, null ];
3154
3155			} else {
3156				match = rquickExpr.exec( selector );
3157			}
3158
3159			// Match html or make sure no context is specified for #id
3160			if ( match && ( match[ 1 ] || !context ) ) {
3161
3162				// HANDLE: $(html) -> $(array)
3163				if ( match[ 1 ] ) {
3164					context = context instanceof jQuery ? context[ 0 ] : context;
3165
3166					// Option to run scripts is true for back-compat
3167					// Intentionally let the error be thrown if parseHTML is not present
3168					jQuery.merge( this, jQuery.parseHTML(
3169						match[ 1 ],
3170						context && context.nodeType ? context.ownerDocument || context : document,
3171						true
3172					) );
3173
3174					// HANDLE: $(html, props)
3175					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
3176						for ( match in context ) {
3177
3178							// Properties of context are called as methods if possible
3179							if ( isFunction( this[ match ] ) ) {
3180								this[ match ]( context[ match ] );
3181
3182							// ...and otherwise set as attributes
3183							} else {
3184								this.attr( match, context[ match ] );
3185							}
3186						}
3187					}
3188
3189					return this;
3190
3191				// HANDLE: $(#id)
3192				} else {
3193					elem = document.getElementById( match[ 2 ] );
3194
3195					if ( elem ) {
3196
3197						// Inject the element directly into the jQuery object
3198						this[ 0 ] = elem;
3199						this.length = 1;
3200					}
3201					return this;
3202				}
3203
3204			// HANDLE: $(expr, $(...))
3205			} else if ( !context || context.jquery ) {
3206				return ( context || root ).find( selector );
3207
3208			// HANDLE: $(expr, context)
3209			// (which is just equivalent to: $(context).find(expr)
3210			} else {
3211				return this.constructor( context ).find( selector );
3212			}
3213
3214		// HANDLE: $(DOMElement)
3215		} else if ( selector.nodeType ) {
3216			this[ 0 ] = selector;
3217			this.length = 1;
3218			return this;
3219
3220		// HANDLE: $(function)
3221		// Shortcut for document ready
3222		} else if ( isFunction( selector ) ) {
3223			return root.ready !== undefined ?
3224				root.ready( selector ) :
3225
3226				// Execute immediately if ready is not present
3227				selector( jQuery );
3228		}
3229
3230		return jQuery.makeArray( selector, this );
3231	};
3232
3233// Give the init function the jQuery prototype for later instantiation
3234init.prototype = jQuery.fn;
3235
3236// Initialize central reference
3237rootjQuery = jQuery( document );
3238
3239
3240var rparentsprev = /^(?:parents|prev(?:Until|All))/,
3241
3242	// Methods guaranteed to produce a unique set when starting from a unique set
3243	guaranteedUnique = {
3244		children: true,
3245		contents: true,
3246		next: true,
3247		prev: true
3248	};
3249
3250jQuery.fn.extend( {
3251	has: function( target ) {
3252		var targets = jQuery( target, this ),
3253			l = targets.length;
3254
3255		return this.filter( function() {
3256			var i = 0;
3257			for ( ; i < l; i++ ) {
3258				if ( jQuery.contains( this, targets[ i ] ) ) {
3259					return true;
3260				}
3261			}
3262		} );
3263	},
3264
3265	closest: function( selectors, context ) {
3266		var cur,
3267			i = 0,
3268			l = this.length,
3269			matched = [],
3270			targets = typeof selectors !== "string" && jQuery( selectors );
3271
3272		// Positional selectors never match, since there's no _selection_ context
3273		if ( !rneedsContext.test( selectors ) ) {
3274			for ( ; i < l; i++ ) {
3275				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
3276
3277					// Always skip document fragments
3278					if ( cur.nodeType < 11 && ( targets ?
3279						targets.index( cur ) > -1 :
3280
3281						// Don't pass non-elements to Sizzle
3282						cur.nodeType === 1 &&
3283							jQuery.find.matchesSelector( cur, selectors ) ) ) {
3284
3285						matched.push( cur );
3286						break;
3287					}
3288				}
3289			}
3290		}
3291
3292		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
3293	},
3294
3295	// Determine the position of an element within the set
3296	index: function( elem ) {
3297
3298		// No argument, return index in parent
3299		if ( !elem ) {
3300			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
3301		}
3302
3303		// Index in selector
3304		if ( typeof elem === "string" ) {
3305			return indexOf.call( jQuery( elem ), this[ 0 ] );
3306		}
3307
3308		// Locate the position of the desired element
3309		return indexOf.call( this,
3310
3311			// If it receives a jQuery object, the first element is used
3312			elem.jquery ? elem[ 0 ] : elem
3313		);
3314	},
3315
3316	add: function( selector, context ) {
3317		return this.pushStack(
3318			jQuery.uniqueSort(
3319				jQuery.merge( this.get(), jQuery( selector, context ) )
3320			)
3321		);
3322	},
3323
3324	addBack: function( selector ) {
3325		return this.add( selector == null ?
3326			this.prevObject : this.prevObject.filter( selector )
3327		);
3328	}
3329} );
3330
3331function sibling( cur, dir ) {
3332	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
3333	return cur;
3334}
3335
3336jQuery.each( {
3337	parent: function( elem ) {
3338		var parent = elem.parentNode;
3339		return parent && parent.nodeType !== 11 ? parent : null;
3340	},
3341	parents: function( elem ) {
3342		return dir( elem, "parentNode" );
3343	},
3344	parentsUntil: function( elem, _i, until ) {
3345		return dir( elem, "parentNode", until );
3346	},
3347	next: function( elem ) {
3348		return sibling( elem, "nextSibling" );
3349	},
3350	prev: function( elem ) {
3351		return sibling( elem, "previousSibling" );
3352	},
3353	nextAll: function( elem ) {
3354		return dir( elem, "nextSibling" );
3355	},
3356	prevAll: function( elem ) {
3357		return dir( elem, "previousSibling" );
3358	},
3359	nextUntil: function( elem, _i, until ) {
3360		return dir( elem, "nextSibling", until );
3361	},
3362	prevUntil: function( elem, _i, until ) {
3363		return dir( elem, "previousSibling", until );
3364	},
3365	siblings: function( elem ) {
3366		return siblings( ( elem.parentNode || {} ).firstChild, elem );
3367	},
3368	children: function( elem ) {
3369		return siblings( elem.firstChild );
3370	},
3371	contents: function( elem ) {
3372		if ( elem.contentDocument != null &&
3373
3374			// Support: IE 11+
3375			// <object> elements with no `data` attribute has an object
3376			// `contentDocument` with a `null` prototype.
3377			getProto( elem.contentDocument ) ) {
3378
3379			return elem.contentDocument;
3380		}
3381
3382		// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
3383		// Treat the template element as a regular one in browsers that
3384		// don't support it.
3385		if ( nodeName( elem, "template" ) ) {
3386			elem = elem.content || elem;
3387		}
3388
3389		return jQuery.merge( [], elem.childNodes );
3390	}
3391}, function( name, fn ) {
3392	jQuery.fn[ name ] = function( until, selector ) {
3393		var matched = jQuery.map( this, fn, until );
3394
3395		if ( name.slice( -5 ) !== "Until" ) {
3396			selector = until;
3397		}
3398
3399		if ( selector && typeof selector === "string" ) {
3400			matched = jQuery.filter( selector, matched );
3401		}
3402
3403		if ( this.length > 1 ) {
3404
3405			// Remove duplicates
3406			if ( !guaranteedUnique[ name ] ) {
3407				jQuery.uniqueSort( matched );
3408			}
3409
3410			// Reverse order for parents* and prev-derivatives
3411			if ( rparentsprev.test( name ) ) {
3412				matched.reverse();
3413			}
3414		}
3415
3416		return this.pushStack( matched );
3417	};
3418} );
3419var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
3420
3421
3422
3423// Convert String-formatted options into Object-formatted ones
3424function createOptions( options ) {
3425	var object = {};
3426	jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
3427		object[ flag ] = true;
3428	} );
3429	return object;
3430}
3431
3432/*
3433 * Create a callback list using the following parameters:
3434 *
3435 *	options: an optional list of space-separated options that will change how
3436 *			the callback list behaves or a more traditional option object
3437 *
3438 * By default a callback list will act like an event callback list and can be
3439 * "fired" multiple times.
3440 *
3441 * Possible options:
3442 *
3443 *	once:			will ensure the callback list can only be fired once (like a Deferred)
3444 *
3445 *	memory:			will keep track of previous values and will call any callback added
3446 *					after the list has been fired right away with the latest "memorized"
3447 *					values (like a Deferred)
3448 *
3449 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
3450 *
3451 *	stopOnFalse:	interrupt callings when a callback returns false
3452 *
3453 */
3454jQuery.Callbacks = function( options ) {
3455
3456	// Convert options from String-formatted to Object-formatted if needed
3457	// (we check in cache first)
3458	options = typeof options === "string" ?
3459		createOptions( options ) :
3460		jQuery.extend( {}, options );
3461
3462	var // Flag to know if list is currently firing
3463		firing,
3464
3465		// Last fire value for non-forgettable lists
3466		memory,
3467
3468		// Flag to know if list was already fired
3469		fired,
3470
3471		// Flag to prevent firing
3472		locked,
3473
3474		// Actual callback list
3475		list = [],
3476
3477		// Queue of execution data for repeatable lists
3478		queue = [],
3479
3480		// Index of currently firing callback (modified by add/remove as needed)
3481		firingIndex = -1,
3482
3483		// Fire callbacks
3484		fire = function() {
3485
3486			// Enforce single-firing
3487			locked = locked || options.once;
3488
3489			// Execute callbacks for all pending executions,
3490			// respecting firingIndex overrides and runtime changes
3491			fired = firing = true;
3492			for ( ; queue.length; firingIndex = -1 ) {
3493				memory = queue.shift();
3494				while ( ++firingIndex < list.length ) {
3495
3496					// Run callback and check for early termination
3497					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
3498						options.stopOnFalse ) {
3499
3500						// Jump to end and forget the data so .add doesn't re-fire
3501						firingIndex = list.length;
3502						memory = false;
3503					}
3504				}
3505			}
3506
3507			// Forget the data if we're done with it
3508			if ( !options.memory ) {
3509				memory = false;
3510			}
3511
3512			firing = false;
3513
3514			// Clean up if we're done firing for good
3515			if ( locked ) {
3516
3517				// Keep an empty list if we have data for future add calls
3518				if ( memory ) {
3519					list = [];
3520
3521				// Otherwise, this object is spent
3522				} else {
3523					list = "";
3524				}
3525			}
3526		},
3527
3528		// Actual Callbacks object
3529		self = {
3530
3531			// Add a callback or a collection of callbacks to the list
3532			add: function() {
3533				if ( list ) {
3534
3535					// If we have memory from a past run, we should fire after adding
3536					if ( memory && !firing ) {
3537						firingIndex = list.length - 1;
3538						queue.push( memory );
3539					}
3540
3541					( function add( args ) {
3542						jQuery.each( args, function( _, arg ) {
3543							if ( isFunction( arg ) ) {
3544								if ( !options.unique || !self.has( arg ) ) {
3545									list.push( arg );
3546								}
3547							} else if ( arg && arg.length && toType( arg ) !== "string" ) {
3548
3549								// Inspect recursively
3550								add( arg );
3551							}
3552						} );
3553					} )( arguments );
3554
3555					if ( memory && !firing ) {
3556						fire();
3557					}
3558				}
3559				return this;
3560			},
3561
3562			// Remove a callback from the list
3563			remove: function() {
3564				jQuery.each( arguments, function( _, arg ) {
3565					var index;
3566					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
3567						list.splice( index, 1 );
3568
3569						// Handle firing indexes
3570						if ( index <= firingIndex ) {
3571							firingIndex--;
3572						}
3573					}
3574				} );
3575				return this;
3576			},
3577
3578			// Check if a given callback is in the list.
3579			// If no argument is given, return whether or not list has callbacks attached.
3580			has: function( fn ) {
3581				return fn ?
3582					jQuery.inArray( fn, list ) > -1 :
3583					list.length > 0;
3584			},
3585
3586			// Remove all callbacks from the list
3587			empty: function() {
3588				if ( list ) {
3589					list = [];
3590				}
3591				return this;
3592			},
3593
3594			// Disable .fire and .add
3595			// Abort any current/pending executions
3596			// Clear all callbacks and values
3597			disable: function() {
3598				locked = queue = [];
3599				list = memory = "";
3600				return this;
3601			},
3602			disabled: function() {
3603				return !list;
3604			},
3605
3606			// Disable .fire
3607			// Also disable .add unless we have memory (since it would have no effect)
3608			// Abort any pending executions
3609			lock: function() {
3610				locked = queue = [];
3611				if ( !memory && !firing ) {
3612					list = memory = "";
3613				}
3614				return this;
3615			},
3616			locked: function() {
3617				return !!locked;
3618			},
3619
3620			// Call all callbacks with the given context and arguments
3621			fireWith: function( context, args ) {
3622				if ( !locked ) {
3623					args = args || [];
3624					args = [ context, args.slice ? args.slice() : args ];
3625					queue.push( args );
3626					if ( !firing ) {
3627						fire();
3628					}
3629				}
3630				return this;
3631			},
3632
3633			// Call all the callbacks with the given arguments
3634			fire: function() {
3635				self.fireWith( this, arguments );
3636				return this;
3637			},
3638
3639			// To know if the callbacks have already been called at least once
3640			fired: function() {
3641				return !!fired;
3642			}
3643		};
3644
3645	return self;
3646};
3647
3648
3649function Identity( v ) {
3650	return v;
3651}
3652function Thrower( ex ) {
3653	throw ex;
3654}
3655
3656function adoptValue( value, resolve, reject, noValue ) {
3657	var method;
3658
3659	try {
3660
3661		// Check for promise aspect first to privilege synchronous behavior
3662		if ( value && isFunction( ( method = value.promise ) ) ) {
3663			method.call( value ).done( resolve ).fail( reject );
3664
3665		// Other thenables
3666		} else if ( value && isFunction( ( method = value.then ) ) ) {
3667			method.call( value, resolve, reject );
3668
3669		// Other non-thenables
3670		} else {
3671
3672			// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
3673			// * false: [ value ].slice( 0 ) => resolve( value )
3674			// * true: [ value ].slice( 1 ) => resolve()
3675			resolve.apply( undefined, [ value ].slice( noValue ) );
3676		}
3677
3678	// For Promises/A+, convert exceptions into rejections
3679	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
3680	// Deferred#then to conditionally suppress rejection.
3681	} catch ( value ) {
3682
3683		// Support: Android 4.0 only
3684		// Strict mode functions invoked without .call/.apply get global-object context
3685		reject.apply( undefined, [ value ] );
3686	}
3687}
3688
3689jQuery.extend( {
3690
3691	Deferred: function( func ) {
3692		var tuples = [
3693
3694				// action, add listener, callbacks,
3695				// ... .then handlers, argument index, [final state]
3696				[ "notify", "progress", jQuery.Callbacks( "memory" ),
3697					jQuery.Callbacks( "memory" ), 2 ],
3698				[ "resolve", "done", jQuery.Callbacks( "once memory" ),
3699					jQuery.Callbacks( "once memory" ), 0, "resolved" ],
3700				[ "reject", "fail", jQuery.Callbacks( "once memory" ),
3701					jQuery.Callbacks( "once memory" ), 1, "rejected" ]
3702			],
3703			state = "pending",
3704			promise = {
3705				state: function() {
3706					return state;
3707				},
3708				always: function() {
3709					deferred.done( arguments ).fail( arguments );
3710					return this;
3711				},
3712				"catch": function( fn ) {
3713					return promise.then( null, fn );
3714				},
3715
3716				// Keep pipe for back-compat
3717				pipe: function( /* fnDone, fnFail, fnProgress */ ) {
3718					var fns = arguments;
3719
3720					return jQuery.Deferred( function( newDefer ) {
3721						jQuery.each( tuples, function( _i, tuple ) {
3722
3723							// Map tuples (progress, done, fail) to arguments (done, fail, progress)
3724							var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
3725
3726							// deferred.progress(function() { bind to newDefer or newDefer.notify })
3727							// deferred.done(function() { bind to newDefer or newDefer.resolve })
3728							// deferred.fail(function() { bind to newDefer or newDefer.reject })
3729							deferred[ tuple[ 1 ] ]( function() {
3730								var returned = fn && fn.apply( this, arguments );
3731								if ( returned && isFunction( returned.promise ) ) {
3732									returned.promise()
3733										.progress( newDefer.notify )
3734										.done( newDefer.resolve )
3735										.fail( newDefer.reject );
3736								} else {
3737									newDefer[ tuple[ 0 ] + "With" ](
3738										this,
3739										fn ? [ returned ] : arguments
3740									);
3741								}
3742							} );
3743						} );
3744						fns = null;
3745					} ).promise();
3746				},
3747				then: function( onFulfilled, onRejected, onProgress ) {
3748					var maxDepth = 0;
3749					function resolve( depth, deferred, handler, special ) {
3750						return function() {
3751							var that = this,
3752								args = arguments,
3753								mightThrow = function() {
3754									var returned, then;
3755
3756									// Support: Promises/A+ section 2.3.3.3.3
3757									// https://promisesaplus.com/#point-59
3758									// Ignore double-resolution attempts
3759									if ( depth < maxDepth ) {
3760										return;
3761									}
3762
3763									returned = handler.apply( that, args );
3764
3765									// Support: Promises/A+ section 2.3.1
3766									// https://promisesaplus.com/#point-48
3767									if ( returned === deferred.promise() ) {
3768										throw new TypeError( "Thenable self-resolution" );
3769									}
3770
3771									// Support: Promises/A+ sections 2.3.3.1, 3.5
3772									// https://promisesaplus.com/#point-54
3773									// https://promisesaplus.com/#point-75
3774									// Retrieve `then` only once
3775									then = returned &&
3776
3777										// Support: Promises/A+ section 2.3.4
3778										// https://promisesaplus.com/#point-64
3779										// Only check objects and functions for thenability
3780										( typeof returned === "object" ||
3781											typeof returned === "function" ) &&
3782										returned.then;
3783
3784									// Handle a returned thenable
3785									if ( isFunction( then ) ) {
3786
3787										// Special processors (notify) just wait for resolution
3788										if ( special ) {
3789											then.call(
3790												returned,
3791												resolve( maxDepth, deferred, Identity, special ),
3792												resolve( maxDepth, deferred, Thrower, special )
3793											);
3794
3795										// Normal processors (resolve) also hook into progress
3796										} else {
3797
3798											// ...and disregard older resolution values
3799											maxDepth++;
3800
3801											then.call(
3802												returned,
3803												resolve( maxDepth, deferred, Identity, special ),
3804												resolve( maxDepth, deferred, Thrower, special ),
3805												resolve( maxDepth, deferred, Identity,
3806													deferred.notifyWith )
3807											);
3808										}
3809
3810									// Handle all other returned values
3811									} else {
3812
3813										// Only substitute handlers pass on context
3814										// and multiple values (non-spec behavior)
3815										if ( handler !== Identity ) {
3816											that = undefined;
3817											args = [ returned ];
3818										}
3819
3820										// Process the value(s)
3821										// Default process is resolve
3822										( special || deferred.resolveWith )( that, args );
3823									}
3824								},
3825
3826								// Only normal processors (resolve) catch and reject exceptions
3827								process = special ?
3828									mightThrow :
3829									function() {
3830										try {
3831											mightThrow();
3832										} catch ( e ) {
3833
3834											if ( jQuery.Deferred.exceptionHook ) {
3835												jQuery.Deferred.exceptionHook( e,
3836													process.stackTrace );
3837											}
3838
3839											// Support: Promises/A+ section 2.3.3.3.4.1
3840											// https://promisesaplus.com/#point-61
3841											// Ignore post-resolution exceptions
3842											if ( depth + 1 >= maxDepth ) {
3843
3844												// Only substitute handlers pass on context
3845												// and multiple values (non-spec behavior)
3846												if ( handler !== Thrower ) {
3847													that = undefined;
3848													args = [ e ];
3849												}
3850
3851												deferred.rejectWith( that, args );
3852											}
3853										}
3854									};
3855
vendor: 7,865 bytes, lines 3856-4174
3856							// Support: Promises/A+ section 2.3.3.3.1
3857							// https://promisesaplus.com/#point-57
3858							// Re-resolve promises immediately to dodge false rejection from
3859							// subsequent errors
3860							if ( depth ) {
3861								process();
3862							} else {
3863
3864								// Call an optional hook to record the stack, in case of exception
3865								// since it's otherwise lost when execution goes async
3866								if ( jQuery.Deferred.getStackHook ) {
3867									process.stackTrace = jQuery.Deferred.getStackHook();
3868								}
3869								window.setTimeout( process );
3870							}
3871						};
3872					}
3873
3874					return jQuery.Deferred( function( newDefer ) {
3875
3876						// progress_handlers.add( ... )
3877						tuples[ 0 ][ 3 ].add(
3878							resolve(
3879								0,
3880								newDefer,
3881								isFunction( onProgress ) ?
3882									onProgress :
3883									Identity,
3884								newDefer.notifyWith
3885							)
3886						);
3887
3888						// fulfilled_handlers.add( ... )
3889						tuples[ 1 ][ 3 ].add(
3890							resolve(
3891								0,
3892								newDefer,
3893								isFunction( onFulfilled ) ?
3894									onFulfilled :
3895									Identity
3896							)
3897						);
3898
3899						// rejected_handlers.add( ... )
3900						tuples[ 2 ][ 3 ].add(
3901							resolve(
3902								0,
3903								newDefer,
3904								isFunction( onRejected ) ?
3905									onRejected :
3906									Thrower
3907							)
3908						);
3909					} ).promise();
3910				},
3911
3912				// Get a promise for this deferred
3913				// If obj is provided, the promise aspect is added to the object
3914				promise: function( obj ) {
3915					return obj != null ? jQuery.extend( obj, promise ) : promise;
3916				}
3917			},
3918			deferred = {};
3919
3920		// Add list-specific methods
3921		jQuery.each( tuples, function( i, tuple ) {
3922			var list = tuple[ 2 ],
3923				stateString = tuple[ 5 ];
3924
3925			// promise.progress = list.add
3926			// promise.done = list.add
3927			// promise.fail = list.add
3928			promise[ tuple[ 1 ] ] = list.add;
3929
3930			// Handle state
3931			if ( stateString ) {
3932				list.add(
3933					function() {
3934
3935						// state = "resolved" (i.e., fulfilled)
3936						// state = "rejected"
3937						state = stateString;
3938					},
3939
3940					// rejected_callbacks.disable
3941					// fulfilled_callbacks.disable
3942					tuples[ 3 - i ][ 2 ].disable,
3943
3944					// rejected_handlers.disable
3945					// fulfilled_handlers.disable
3946					tuples[ 3 - i ][ 3 ].disable,
3947
3948					// progress_callbacks.lock
3949					tuples[ 0 ][ 2 ].lock,
3950
3951					// progress_handlers.lock
3952					tuples[ 0 ][ 3 ].lock
3953				);
3954			}
3955
3956			// progress_handlers.fire
3957			// fulfilled_handlers.fire
3958			// rejected_handlers.fire
3959			list.add( tuple[ 3 ].fire );
3960
3961			// deferred.notify = function() { deferred.notifyWith(...) }
3962			// deferred.resolve = function() { deferred.resolveWith(...) }
3963			// deferred.reject = function() { deferred.rejectWith(...) }
3964			deferred[ tuple[ 0 ] ] = function() {
3965				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
3966				return this;
3967			};
3968
3969			// deferred.notifyWith = list.fireWith
3970			// deferred.resolveWith = list.fireWith
3971			// deferred.rejectWith = list.fireWith
3972			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
3973		} );
3974
3975		// Make the deferred a promise
3976		promise.promise( deferred );
3977
3978		// Call given func if any
3979		if ( func ) {
3980			func.call( deferred, deferred );
3981		}
3982
3983		// All done!
3984		return deferred;
3985	},
3986
3987	// Deferred helper
3988	when: function( singleValue ) {
3989		var
3990
3991			// count of uncompleted subordinates
3992			remaining = arguments.length,
3993
3994			// count of unprocessed arguments
3995			i = remaining,
3996
3997			// subordinate fulfillment data
3998			resolveContexts = Array( i ),
3999			resolveValues = slice.call( arguments ),
4000
4001			// the master Deferred
4002			master = jQuery.Deferred(),
4003
4004			// subordinate callback factory
4005			updateFunc = function( i ) {
4006				return function( value ) {
4007					resolveContexts[ i ] = this;
4008					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
4009					if ( !( --remaining ) ) {
4010						master.resolveWith( resolveContexts, resolveValues );
4011					}
4012				};
4013			};
4014
4015		// Single- and empty arguments are adopted like Promise.resolve
4016		if ( remaining <= 1 ) {
4017			adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
4018				!remaining );
4019
4020			// Use .then() to unwrap secondary thenables (cf. gh-3000)
4021			if ( master.state() === "pending" ||
4022				isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
4023
4024				return master.then();
4025			}
4026		}
4027
4028		// Multiple arguments are aggregated like Promise.all array elements
4029		while ( i-- ) {
4030			adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
4031		}
4032
4033		return master.promise();
4034	}
4035} );
4036
4037
4038// These usually indicate a programmer mistake during development,
4039// warn about them ASAP rather than swallowing them by default.
4040var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
4041
4042jQuery.Deferred.exceptionHook = function( error, stack ) {
4043
4044	// Support: IE 8 - 9 only
4045	// Console exists when dev tools are open, which can happen at any time
4046	if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
4047		window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
4048	}
4049};
4050
4051
4052
4053
4054jQuery.readyException = function( error ) {
4055	window.setTimeout( function() {
4056		throw error;
4057	} );
4058};
4059
4060
4061
4062
4063// The deferred used on DOM ready
4064var readyList = jQuery.Deferred();
4065
4066jQuery.fn.ready = function( fn ) {
4067
4068	readyList
4069		.then( fn )
4070
4071		// Wrap jQuery.readyException in a function so that the lookup
4072		// happens at the time of error handling instead of callback
4073		// registration.
4074		.catch( function( error ) {
4075			jQuery.readyException( error );
4076		} );
4077
4078	return this;
4079};
4080
4081jQuery.extend( {
4082
4083	// Is the DOM ready to be used? Set to true once it occurs.
4084	isReady: false,
4085
4086	// A counter to track how many items to wait for before
4087	// the ready event fires. See #6781
4088	readyWait: 1,
4089
4090	// Handle when the DOM is ready
4091	ready: function( wait ) {
4092
4093		// Abort if there are pending holds or we're already ready
4094		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
4095			return;
4096		}
4097
4098		// Remember that the DOM is ready
4099		jQuery.isReady = true;
4100
4101		// If a normal DOM Ready event fired, decrement, and wait if need be
4102		if ( wait !== true && --jQuery.readyWait > 0 ) {
4103			return;
4104		}
4105
4106		// If there are functions bound, to execute
4107		readyList.resolveWith( document, [ jQuery ] );
4108	}
4109} );
4110
4111jQuery.ready.then = readyList.then;
4112
4113// The ready event handler and self cleanup method
4114function completed() {
4115	document.removeEventListener( "DOMContentLoaded", completed );
4116	window.removeEventListener( "load", completed );
4117	jQuery.ready();
4118}
4119
4120// Catch cases where $(document).ready() is called
4121// after the browser event has already occurred.
4122// Support: IE <=9 - 10 only
4123// Older IE sometimes signals "interactive" too soon
4124if ( document.readyState === "complete" ||
4125	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
4126
4127	// Handle it asynchronously to allow scripts the opportunity to delay ready
4128	window.setTimeout( jQuery.ready );
4129
4130} else {
4131
4132	// Use the handy event callback
4133	document.addEventListener( "DOMContentLoaded", completed );
4134
4135	// A fallback to window.onload, that will always work
4136	window.addEventListener( "load", completed );
4137}
4138
4139
4140
4141
4142// Multifunctional method to get and set values of a collection
4143// The value/s can optionally be executed if it's a function
4144var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
4145	var i = 0,
4146		len = elems.length,
4147		bulk = key == null;
4148
4149	// Sets many values
4150	if ( toType( key ) === "object" ) {
4151		chainable = true;
4152		for ( i in key ) {
4153			access( elems, fn, i, key[ i ], true, emptyGet, raw );
4154		}
4155
4156	// Sets one value
4157	} else if ( value !== undefined ) {
4158		chainable = true;
4159
4160		if ( !isFunction( value ) ) {
4161			raw = true;
4162		}
4163
4164		if ( bulk ) {
4165
4166			// Bulk operations run against the entire set
4167			if ( raw ) {
4168				fn.call( elems, value );
4169				fn = null;
4170
4171			// ...except when executing function values
4172			} else {
4173				bulk = fn;
4174				fn = function( elem, 
4174_key, value ) {
4175					return bulk.call( jQuery( elem ), value );
4176				};
4177			}
4178		}
4179
4180		if ( fn ) {
4181			for ( ; i < len; i++ ) {
4182				fn(
4183					elems[ i ], key, raw ?
4184					value :
vendor: 22,305 bytes, lines 4185-5093
4185					value.call( elems[ i ], i, fn( elems[ i ], key ) )
4186				);
4187			}
4188		}
4189	}
4190
4191	if ( chainable ) {
4192		return elems;
4193	}
4194
4195	// Gets
4196	if ( bulk ) {
4197		return fn.call( elems );
4198	}
4199
4200	return len ? fn( elems[ 0 ], key ) : emptyGet;
4201};
4202
4203
4204// Matches dashed string for camelizing
4205var rmsPrefix = /^-ms-/,
4206	rdashAlpha = /-([a-z])/g;
4207
4208// Used by camelCase as callback to replace()
4209function fcamelCase( _all, letter ) {
4210	return letter.toUpperCase();
4211}
4212
4213// Convert dashed to camelCase; used by the css and data modules
4214// Support: IE <=9 - 11, Edge 12 - 15
4215// Microsoft forgot to hump their vendor prefix (#9572)
4216function camelCase( string ) {
4217	return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
4218}
4219var acceptData = function( owner ) {
4220
4221	// Accepts only:
4222	//  - Node
4223	//    - Node.ELEMENT_NODE
4224	//    - Node.DOCUMENT_NODE
4225	//  - Object
4226	//    - Any
4227	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
4228};
4229
4230
4231
4232
4233function Data() {
4234	this.expando = jQuery.expando + Data.uid++;
4235}
4236
4237Data.uid = 1;
4238
4239Data.prototype = {
4240
4241	cache: function( owner ) {
4242
4243		// Check if the owner object already has a cache
4244		var value = owner[ this.expando ];
4245
4246		// If not, create one
4247		if ( !value ) {
4248			value = {};
4249
4250			// We can accept data for non-element nodes in modern browsers,
4251			// but we should not, see #8335.
4252			// Always return an empty object.
4253			if ( acceptData( owner ) ) {
4254
4255				// If it is a node unlikely to be stringify-ed or looped over
4256				// use plain assignment
4257				if ( owner.nodeType ) {
4258					owner[ this.expando ] = value;
4259
4260				// Otherwise secure it in a non-enumerable property
4261				// configurable must be true to allow the property to be
4262				// deleted when data is removed
4263				} else {
4264					Object.defineProperty( owner, this.expando, {
4265						value: value,
4266						configurable: true
4267					} );
4268				}
4269			}
4270		}
4271
4272		return value;
4273	},
4274	set: function( owner, data, value ) {
4275		var prop,
4276			cache = this.cache( owner );
4277
4278		// Handle: [ owner, key, value ] args
4279		// Always use camelCase key (gh-2257)
4280		if ( typeof data === "string" ) {
4281			cache[ camelCase( data ) ] = value;
4282
4283		// Handle: [ owner, { properties } ] args
4284		} else {
4285
4286			// Copy the properties one-by-one to the cache object
4287			for ( prop in data ) {
4288				cache[ camelCase( prop ) ] = data[ prop ];
4289			}
4290		}
4291		return cache;
4292	},
4293	get: function( owner, key ) {
4294		return key === undefined ?
4295			this.cache( owner ) :
4296
4297			// Always use camelCase key (gh-2257)
4298			owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
4299	},
4300	access: function( owner, key, value ) {
4301
4302		// In cases where either:
4303		//
4304		//   1. No key was specified
4305		//   2. A string key was specified, but no value provided
4306		//
4307		// Take the "read" path and allow the get method to determine
4308		// which value to return, respectively either:
4309		//
4310		//   1. The entire cache object
4311		//   2. The data stored at the key
4312		//
4313		if ( key === undefined ||
4314				( ( key && typeof key === "string" ) && value === undefined ) ) {
4315
4316			return this.get( owner, key );
4317		}
4318
4319		// When the key is not a string, or both a key and value
4320		// are specified, set or extend (existing objects) with either:
4321		//
4322		//   1. An object of properties
4323		//   2. A key and value
4324		//
4325		this.set( owner, key, value );
4326
4327		// Since the "set" path can have two possible entry points
4328		// return the expected data based on which path was taken[*]
4329		return value !== undefined ? value : key;
4330	},
4331	remove: function( owner, key ) {
4332		var i,
4333			cache = owner[ this.expando ];
4334
4335		if ( cache === undefined ) {
4336			return;
4337		}
4338
4339		if ( key !== undefined ) {
4340
4341			// Support array or space separated string of keys
4342			if ( Array.isArray( key ) ) {
4343
4344				// If key is an array of keys...
4345				// We always set camelCase keys, so remove that.
4346				key = key.map( camelCase );
4347			} else {
4348				key = camelCase( key );
4349
4350				// If a key with the spaces exists, use it.
4351				// Otherwise, create an array by matching non-whitespace
4352				key = key in cache ?
4353					[ key ] :
4354					( key.match( rnothtmlwhite ) || [] );
4355			}
4356
4357			i = key.length;
4358
4359			while ( i-- ) {
4360				delete cache[ key[ i ] ];
4361			}
4362		}
4363
4364		// Remove the expando if there's no more data
4365		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
4366
4367			// Support: Chrome <=35 - 45
4368			// Webkit & Blink performance suffers when deleting properties
4369			// from DOM nodes, so set to undefined instead
4370			// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
4371			if ( owner.nodeType ) {
4372				owner[ this.expando ] = undefined;
4373			} else {
4374				delete owner[ this.expando ];
4375			}
4376		}
4377	},
4378	hasData: function( owner ) {
4379		var cache = owner[ this.expando ];
4380		return cache !== undefined && !jQuery.isEmptyObject( cache );
4381	}
4382};
4383var dataPriv = new Data();
4384
4385var dataUser = new Data();
4386
4387
4388
4389//	Implementation Summary
4390//
4391//	1. Enforce API surface and semantic compatibility with 1.9.x branch
4392//	2. Improve the module's maintainability by reducing the storage
4393//		paths to a single mechanism.
4394//	3. Use the same single mechanism to support "private" and "user" data.
4395//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
4396//	5. Avoid exposing implementation details on user objects (eg. expando properties)
4397//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
4398
4399var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
4400	rmultiDash = /[A-Z]/g;
4401
4402function getData( data ) {
4403	if ( data === "true" ) {
4404		return true;
4405	}
4406
4407	if ( data === "false" ) {
4408		return false;
4409	}
4410
4411	if ( data === "null" ) {
4412		return null;
4413	}
4414
4415	// Only convert to a number if it doesn't change the string
4416	if ( data === +data + "" ) {
4417		return +data;
4418	}
4419
4420	if ( rbrace.test( data ) ) {
4421		return JSON.parse( data );
4422	}
4423
4424	return data;
4425}
4426
4427function dataAttr( elem, key, data ) {
4428	var name;
4429
4430	// If nothing was found internally, try to fetch any
4431	// data from the HTML5 data-* attribute
4432	if ( data === undefined && elem.nodeType === 1 ) {
4433		name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
4434		data = elem.getAttribute( name );
4435
4436		if ( typeof data === "string" ) {
4437			try {
4438				data = getData( data );
4439			} catch ( e ) {}
4440
4441			// Make sure we set the data so it isn't changed later
4442			dataUser.set( elem, key, data );
4443		} else {
4444			data = undefined;
4445		}
4446	}
4447	return data;
4448}
4449
4450jQuery.extend( {
4451	hasData: function( elem ) {
4452		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
4453	},
4454
4455	data: function( elem, name, data ) {
4456		return dataUser.access( elem, name, data );
4457	},
4458
4459	removeData: function( elem, name ) {
4460		dataUser.remove( elem, name );
4461	},
4462
4463	// TODO: Now that all calls to _data and _removeData have been replaced
4464	// with direct calls to dataPriv methods, these can be deprecated.
4465	_data: function( elem, name, data ) {
4466		return dataPriv.access( elem, name, data );
4467	},
4468
4469	_removeData: function( elem, name ) {
4470		dataPriv.remove( elem, name );
4471	}
4472} );
4473
4474jQuery.fn.extend( {
4475	data: function( key, value ) {
4476		var i, name, data,
4477			elem = this[ 0 ],
4478			attrs = elem && elem.attributes;
4479
4480		// Gets all values
4481		if ( key === undefined ) {
4482			if ( this.length ) {
4483				data = dataUser.get( elem );
4484
4485				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
4486					i = attrs.length;
4487					while ( i-- ) {
4488
4489						// Support: IE 11 only
4490						// The attrs elements can be null (#14894)
4491						if ( attrs[ i ] ) {
4492							name = attrs[ i ].name;
4493							if ( name.indexOf( "data-" ) === 0 ) {
4494								name = camelCase( name.slice( 5 ) );
4495								dataAttr( elem, name, data[ name ] );
4496							}
4497						}
4498					}
4499					dataPriv.set( elem, "hasDataAttrs", true );
4500				}
4501			}
4502
4503			return data;
4504		}
4505
4506		// Sets multiple values
4507		if ( typeof key === "object" ) {
4508			return this.each( function() {
4509				dataUser.set( this, key );
4510			} );
4511		}
4512
4513		return access( this, function( value ) {
4514			var data;
4515
4516			// The calling jQuery object (element matches) is not empty
4517			// (and therefore has an element appears at this[ 0 ]) and the
4518			// `value` parameter was not undefined. An empty jQuery object
4519			// will result in `undefined` for elem = this[ 0 ] which will
4520			// throw an exception if an attempt to read a data cache is made.
4521			if ( elem && value === undefined ) {
4522
4523				// Attempt to get data from the cache
4524				// The key will always be camelCased in Data
4525				data = dataUser.get( elem, key );
4526				if ( data !== undefined ) {
4527					return data;
4528				}
4529
4530				// Attempt to "discover" the data in
4531				// HTML5 custom data-* attrs
4532				data = dataAttr( elem, key );
4533				if ( data !== undefined ) {
4534					return data;
4535				}
4536
4537				// We tried really hard, but the data doesn't exist.
4538				return;
4539			}
4540
4541			// Set the data...
4542			this.each( function() {
4543
4544				// We always store the camelCased key
4545				dataUser.set( this, key, value );
4546			} );
4547		}, null, value, arguments.length > 1, null, true );
4548	},
4549
4550	removeData: function( key ) {
4551		return this.each( function() {
4552			dataUser.remove( this, key );
4553		} );
4554	}
4555} );
4556
4557
4558jQuery.extend( {
4559	queue: function( elem, type, data ) {
4560		var queue;
4561
4562		if ( elem ) {
4563			type = ( type || "fx" ) + "queue";
4564			queue = dataPriv.get( elem, type );
4565
4566			// Speed up dequeue by getting out quickly if this is just a lookup
4567			if ( data ) {
4568				if ( !queue || Array.isArray( data ) ) {
4569					queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
4570				} else {
4571					queue.push( data );
4572				}
4573			}
4574			return queue || [];
4575		}
4576	},
4577
4578	dequeue: function( elem, type ) {
4579		type = type || "fx";
4580
4581		var queue = jQuery.queue( elem, type ),
4582			startLength = queue.length,
4583			fn = queue.shift(),
4584			hooks = jQuery._queueHooks( elem, type ),
4585			next = function() {
4586				jQuery.dequeue( elem, type );
4587			};
4588
4589		// If the fx queue is dequeued, always remove the progress sentinel
4590		if ( fn === "inprogress" ) {
4591			fn = queue.shift();
4592			startLength--;
4593		}
4594
4595		if ( fn ) {
4596
4597			// Add a progress sentinel to prevent the fx queue from being
4598			// automatically dequeued
4599			if ( type === "fx" ) {
4600				queue.unshift( "inprogress" );
4601			}
4602
4603			// Clear up the last queue stop function
4604			delete hooks.stop;
4605			fn.call( elem, next, hooks );
4606		}
4607
4608		if ( !startLength && hooks ) {
4609			hooks.empty.fire();
4610		}
4611	},
4612
4613	// Not public - generate a queueHooks object, or return the current one
4614	_queueHooks: function( elem, type ) {
4615		var key = type + "queueHooks";
4616		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
4617			empty: jQuery.Callbacks( "once memory" ).add( function() {
4618				dataPriv.remove( elem, [ type + "queue", key ] );
4619			} )
4620		} );
4621	}
4622} );
4623
4624jQuery.fn.extend( {
4625	queue: function( type, data ) {
4626		var setter = 2;
4627
4628		if ( typeof type !== "string" ) {
4629			data = type;
4630			type = "fx";
4631			setter--;
4632		}
4633
4634		if ( arguments.length < setter ) {
4635			return jQuery.queue( this[ 0 ], type );
4636		}
4637
4638		return data === undefined ?
4639			this :
4640			this.each( function() {
4641				var queue = jQuery.queue( this, type, data );
4642
4643				// Ensure a hooks for this queue
4644				jQuery._queueHooks( this, type );
4645
4646				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
4647					jQuery.dequeue( this, type );
4648				}
4649			} );
4650	},
4651	dequeue: function( type ) {
4652		return this.each( function() {
4653			jQuery.dequeue( this, type );
4654		} );
4655	},
4656	clearQueue: function( type ) {
4657		return this.queue( type || "fx", [] );
4658	},
4659
4660	// Get a promise resolved when queues of a certain type
4661	// are emptied (fx is the type by default)
4662	promise: function( type, obj ) {
4663		var tmp,
4664			count = 1,
4665			defer = jQuery.Deferred(),
4666			elements = this,
4667			i = this.length,
4668			resolve = function() {
4669				if ( !( --count ) ) {
4670					defer.resolveWith( elements, [ elements ] );
4671				}
4672			};
4673
4674		if ( typeof type !== "string" ) {
4675			obj = type;
4676			type = undefined;
4677		}
4678		type = type || "fx";
4679
4680		while ( i-- ) {
4681			tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
4682			if ( tmp && tmp.empty ) {
4683				count++;
4684				tmp.empty.add( resolve );
4685			}
4686		}
4687		resolve();
4688		return defer.promise( obj );
4689	}
4690} );
4691var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
4692
4693var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
4694
4695
4696var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
4697
4698var documentElement = document.documentElement;
4699
4700
4701
4702	var isAttached = function( elem ) {
4703			return jQuery.contains( elem.ownerDocument, elem );
4704		},
4705		composed = { composed: true };
4706
4707	// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
4708	// Check attachment across shadow DOM boundaries when possible (gh-3504)
4709	// Support: iOS 10.0-10.2 only
4710	// Early iOS 10 versions support `attachShadow` but not `getRootNode`,
4711	// leading to errors. We need to check for `getRootNode`.
4712	if ( documentElement.getRootNode ) {
4713		isAttached = function( elem ) {
4714			return jQuery.contains( elem.ownerDocument, elem ) ||
4715				elem.getRootNode( composed ) === elem.ownerDocument;
4716		};
4717	}
4718var isHiddenWithinTree = function( elem, el ) {
4719
4720		// isHiddenWithinTree might be called from jQuery#filter function;
4721		// in that case, element will be second argument
4722		elem = el || elem;
4723
4724		// Inline style trumps all
4725		return elem.style.display === "none" ||
4726			elem.style.display === "" &&
4727
4728			// Otherwise, check computed style
4729			// Support: Firefox <=43 - 45
4730			// Disconnected elements can have computed display: none, so first confirm that elem is
4731			// in the document.
4732			isAttached( elem ) &&
4733
4734			jQuery.css( elem, "display" ) === "none";
4735	};
4736
4737
4738
4739function adjustCSS( elem, prop, valueParts, tween ) {
4740	var adjusted, scale,
4741		maxIterations = 20,
4742		currentValue = tween ?
4743			function() {
4744				return tween.cur();
4745			} :
4746			function() {
4747				return jQuery.css( elem, prop, "" );
4748			},
4749		initial = currentValue(),
4750		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
4751
4752		// Starting value computation is required for potential unit mismatches
4753		initialInUnit = elem.nodeType &&
4754			( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
4755			rcssNum.exec( jQuery.css( elem, prop ) );
4756
4757	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
4758
4759		// Support: Firefox <=54
4760		// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
4761		initial = initial / 2;
4762
4763		// Trust units reported by jQuery.css
4764		unit = unit || initialInUnit[ 3 ];
4765
4766		// Iteratively approximate from a nonzero starting point
4767		initialInUnit = +initial || 1;
4768
4769		while ( maxIterations-- ) {
4770
4771			// Evaluate and update our best guess (doubling guesses that zero out).
4772			// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
4773			jQuery.style( elem, prop, initialInUnit + unit );
4774			if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
4775				maxIterations = 0;
4776			}
4777			initialInUnit = initialInUnit / scale;
4778
4779		}
4780
4781		initialInUnit = initialInUnit * 2;
4782		jQuery.style( elem, prop, initialInUnit + unit );
4783
4784		// Make sure we update the tween properties later on
4785		valueParts = valueParts || [];
4786	}
4787
4788	if ( valueParts ) {
4789		initialInUnit = +initialInUnit || +initial || 0;
4790
4791		// Apply relative offset (+=/-=) if specified
4792		adjusted = valueParts[ 1 ] ?
4793			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
4794			+valueParts[ 2 ];
4795		if ( tween ) {
4796			tween.unit = unit;
4797			tween.start = initialInUnit;
4798			tween.end = adjusted;
4799		}
4800	}
4801	return adjusted;
4802}
4803
4804
4805var defaultDisplayMap = {};
4806
4807function getDefaultDisplay( elem ) {
4808	var temp,
4809		doc = elem.ownerDocument,
4810		nodeName = elem.nodeName,
4811		display = defaultDisplayMap[ nodeName ];
4812
4813	if ( display ) {
4814		return display;
4815	}
4816
4817	temp = doc.body.appendChild( doc.createElement( nodeName ) );
4818	display = jQuery.css( temp, "display" );
4819
4820	temp.parentNode.removeChild( temp );
4821
4822	if ( display === "none" ) {
4823		display = "block";
4824	}
4825	defaultDisplayMap[ nodeName ] = display;
4826
4827	return display;
4828}
4829
4830function showHide( elements, show ) {
4831	var display, elem,
4832		values = [],
4833		index = 0,
4834		length = elements.length;
4835
4836	// Determine new display value for elements that need to change
4837	for ( ; index < length; index++ ) {
4838		elem = elements[ index ];
4839		if ( !elem.style ) {
4840			continue;
4841		}
4842
4843		display = elem.style.display;
4844		if ( show ) {
4845
4846			// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
4847			// check is required in this first loop unless we have a nonempty display value (either
4848			// inline or about-to-be-restored)
4849			if ( display === "none" ) {
4850				values[ index ] = dataPriv.get( elem, "display" ) || null;
4851				if ( !values[ index ] ) {
4852					elem.style.display = "";
4853				}
4854			}
4855			if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
4856				values[ index ] = getDefaultDisplay( elem );
4857			}
4858		} else {
4859			if ( display !== "none" ) {
4860				values[ index ] = "none";
4861
4862				// Remember what we're overwriting
4863				dataPriv.set( elem, "display", display );
4864			}
4865		}
4866	}
4867
4868	// Set the display of the elements in a second loop to avoid constant reflow
4869	for ( index = 0; index < length; index++ ) {
4870		if ( values[ index ] != null ) {
4871			elements[ index ].style.display = values[ index ];
4872		}
4873	}
4874
4875	return elements;
4876}
4877
4878jQuery.fn.extend( {
4879	show: function() {
4880		return showHide( this, true );
4881	},
4882	hide: function() {
4883		return showHide( this );
4884	},
4885	toggle: function( state ) {
4886		if ( typeof state === "boolean" ) {
4887			return state ? this.show() : this.hide();
4888		}
4889
4890		return this.each( function() {
4891			if ( isHiddenWithinTree( this ) ) {
4892				jQuery( this ).show();
4893			} else {
4894				jQuery( this ).hide();
4895			}
4896		} );
4897	}
4898} );
4899var rcheckableType = ( /^(?:checkbox|radio)$/i );
4900
4901var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
4902
4903var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
4904
4905
4906
4907( function() {
4908	var fragment = document.createDocumentFragment(),
4909		div = fragment.appendChild( document.createElement( "div" ) ),
4910		input = document.createElement( "input" );
4911
4912	// Support: Android 4.0 - 4.3 only
4913	// Check state lost if the name is set (#11217)
4914	// Support: Windows Web Apps (WWA)
4915	// `name` and `type` must use .setAttribute for WWA (#14901)
4916	input.setAttribute( "type", "radio" );
4917	input.setAttribute( "checked", "checked" );
4918	input.setAttribute( "name", "t" );
4919
4920	div.appendChild( input );
4921
4922	// Support: Android <=4.1 only
4923	// Older WebKit doesn't clone checked state correctly in fragments
4924	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
4925
4926	// Support: IE <=11 only
4927	// Make sure textarea (and checkbox) defaultValue is properly cloned
4928	div.innerHTML = "<textarea>x</textarea>";
4929	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
4930
4931	// Support: IE <=9 only
4932	// IE <=9 replaces <option> tags with their contents when inserted outside of
4933	// the select element.
4934	div.innerHTML = "<option></option>";
4935	support.option = !!div.lastChild;
4936} )();
4937
4938
4939// We have to close these tags to support XHTML (#13200)
4940var wrapMap = {
4941
4942	// XHTML parsers do not magically insert elements in the
4943	// same way that tag soup parsers do. So we cannot shorten
4944	// this by omitting <tbody> or other required elements.
4945	thead: [ 1, "<table>", "</table>" ],
4946	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
4947	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
4948	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
4949
4950	_default: [ 0, "", "" ]
4951};
4952
4953wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
4954wrapMap.th = wrapMap.td;
4955
4956// Support: IE <=9 only
4957if ( !support.option ) {
4958	wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
4959}
4960
4961
4962function getAll( context, tag ) {
4963
4964	// Support: IE <=9 - 11 only
4965	// Use typeof to avoid zero-argument method invocation on host objects (#15151)
4966	var ret;
4967
4968	if ( typeof context.getElementsByTagName !== "undefined" ) {
4969		ret = context.getElementsByTagName( tag || "*" );
4970
4971	} else if ( typeof context.querySelectorAll !== "undefined" ) {
4972		ret = context.querySelectorAll( tag || "*" );
4973
4974	} else {
4975		ret = [];
4976	}
4977
4978	if ( tag === undefined || tag && nodeName( context, tag ) ) {
4979		return jQuery.merge( [ context ], ret );
4980	}
4981
4982	return ret;
4983}
4984
4985
4986// Mark scripts as having already been evaluated
4987function setGlobalEval( elems, refElements ) {
4988	var i = 0,
4989		l = elems.length;
4990
4991	for ( ; i < l; i++ ) {
4992		dataPriv.set(
4993			elems[ i ],
4994			"globalEval",
4995			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
4996		);
4997	}
4998}
4999
5000
5001var rhtml = /<|&#?\w+;/;
5002
5003function buildFragment( elems, context, scripts, selection, ignored ) {
5004	var elem, tmp, tag, wrap, attached, j,
5005		fragment = context.createDocumentFragment(),
5006		nodes = [],
5007		i = 0,
5008		l = elems.length;
5009
5010	for ( ; i < l; i++ ) {
5011		elem = elems[ i ];
5012
5013		if ( elem || elem === 0 ) {
5014
5015			// Add nodes directly
5016			if ( toType( elem ) === "object" ) {
5017
5018				// Support: Android <=4.0 only, PhantomJS 1 only
5019				// push.apply(_, arraylike) throws on ancient WebKit
5020				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
5021
5022			// Convert non-html into a text node
5023			} else if ( !rhtml.test( elem ) ) {
5024				nodes.push( context.createTextNode( elem ) );
5025
5026			// Convert html into DOM nodes
5027			} else {
5028				tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
5029
5030				// Deserialize a standard representation
5031				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
5032				wrap = wrapMap[ tag ] || wrapMap._default;
5033				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
5034
5035				// Descend through wrappers to the right content
5036				j = wrap[ 0 ];
5037				while ( j-- ) {
5038					tmp = tmp.lastChild;
5039				}
5040
5041				// Support: Android <=4.0 only, PhantomJS 1 only
5042				// push.apply(_, arraylike) throws on ancient WebKit
5043				jQuery.merge( nodes, tmp.childNodes );
5044
5045				// Remember the top-level container
5046				tmp = fragment.firstChild;
5047
5048				// Ensure the created nodes are orphaned (#12392)
5049				tmp.textContent = "";
5050			}
5051		}
5052	}
5053
5054	// Remove wrapper from fragment
5055	fragment.textContent = "";
5056
5057	i = 0;
5058	while ( ( elem = nodes[ i++ ] ) ) {
5059
5060		// Skip elements already in the context collection (trac-4087)
5061		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
5062			if ( ignored ) {
5063				ignored.push( elem );
5064			}
5065			continue;
5066		}
5067
5068		attached = isAttached( elem );
5069
5070		// Append to fragment
5071		tmp = getAll( fragment.appendChild( elem ), "script" );
5072
5073		// Preserve script evaluation history
5074		if ( attached ) {
5075			setGlobalEval( tmp );
5076		}
5077
5078		// Capture executables
5079		if ( scripts ) {
5080			j = 0;
5081			while ( ( elem = tmp[ j++ ] ) ) {
5082				if ( rscriptType.test( elem.type || "" ) ) {
5083					scripts.push( elem );
5084				}
5085			}
5086		}
5087	}
5088
5089	return fragment;
5090}
5091
5092
5093var
5093
vendor: 6,183 bytes, lines 5094-5319
5094	rkeyEvent = /^key/,
5095	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
5096	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
5097
5098function returnTrue() {
5099	return true;
5100}
5101
5102function returnFalse() {
5103	return false;
5104}
5105
5106// Support: IE <=9 - 11+
5107// focus() and blur() are asynchronous, except when they are no-op.
5108// So expect focus to be synchronous when the element is already active,
5109// and blur to be synchronous when the element is not already active.
5110// (focus and blur are always synchronous in other supported browsers,
5111// this just defines when we can count on it).
5112function expectSync( elem, type ) {
5113	return ( elem === safeActiveElement() ) === ( type === "focus" );
5114}
5115
5116// Support: IE <=9 only
5117// Accessing document.activeElement can throw unexpectedly
5118// https://bugs.jquery.com/ticket/13393
5119function safeActiveElement() {
5120	try {
5121		return document.activeElement;
5122	} catch ( err ) { }
5123}
5124
5125function on( elem, types, selector, data, fn, one ) {
5126	var origFn, type;
5127
5128	// Types can be a map of types/handlers
5129	if ( typeof types === "object" ) {
5130
5131		// ( types-Object, selector, data )
5132		if ( typeof selector !== "string" ) {
5133
5134			// ( types-Object, data )
5135			data = data || selector;
5136			selector = undefined;
5137		}
5138		for ( type in types ) {
5139			on( elem, type, selector, data, types[ type ], one );
5140		}
5141		return elem;
5142	}
5143
5144	if ( data == null && fn == null ) {
5145
5146		// ( types, fn )
5147		fn = selector;
5148		data = selector = undefined;
5149	} else if ( fn == null ) {
5150		if ( typeof selector === "string" ) {
5151
5152			// ( types, selector, fn )
5153			fn = data;
5154			data = undefined;
5155		} else {
5156
5157			// ( types, data, fn )
5158			fn = data;
5159			data = selector;
5160			selector = undefined;
5161		}
5162	}
5163	if ( fn === false ) {
5164		fn = returnFalse;
5165	} else if ( !fn ) {
5166		return elem;
5167	}
5168
5169	if ( one === 1 ) {
5170		origFn = fn;
5171		fn = function( event ) {
5172
5173			// Can use an empty set, since event contains the info
5174			jQuery().off( event );
5175			return origFn.apply( this, arguments );
5176		};
5177
5178		// Use same guid so caller can remove using origFn
5179		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
5180	}
5181	return elem.each( function() {
5182		jQuery.event.add( this, types, fn, data, selector );
5183	} );
5184}
5185
5186/*
5187 * Helper functions for managing events -- not part of the public interface.
5188 * Props to Dean Edwards' addEvent library for many of the ideas.
5189 */
5190jQuery.event = {
5191
5192	global: {},
5193
5194	add: function( elem, types, handler, data, selector ) {
5195
5196		var handleObjIn, eventHandle, tmp,
5197			events, t, handleObj,
5198			special, handlers, type, namespaces, origType,
5199			elemData = dataPriv.get( elem );
5200
5201		// Only attach events to objects that accept data
5202		if ( !acceptData( elem ) ) {
5203			return;
5204		}
5205
5206		// Caller can pass in an object of custom data in lieu of the handler
5207		if ( handler.handler ) {
5208			handleObjIn = handler;
5209			handler = handleObjIn.handler;
5210			selector = handleObjIn.selector;
5211		}
5212
5213		// Ensure that invalid selectors throw exceptions at attach time
5214		// Evaluate against documentElement in case elem is a non-element node (e.g., document)
5215		if ( selector ) {
5216			jQuery.find.matchesSelector( documentElement, selector );
5217		}
5218
5219		// Make sure that the handler has a unique ID, used to find/remove it later
5220		if ( !handler.guid ) {
5221			handler.guid = jQuery.guid++;
5222		}
5223
5224		// Init the element's event structure and main handler, if this is the first
5225		if ( !( events = elemData.events ) ) {
5226			events = elemData.events = Object.create( null );
5227		}
5228		if ( !( eventHandle = elemData.handle ) ) {
5229			eventHandle = elemData.handle = function( e ) {
5230
5231				// Discard the second event of a jQuery.event.trigger() and
5232				// when an event is called after a page has unloaded
5233				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
5234					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
5235			};
5236		}
5237
5238		// Handle multiple events separated by a space
5239		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
5240		t = types.length;
5241		while ( t-- ) {
5242			tmp = rtypenamespace.exec( types[ t ] ) || [];
5243			type = origType = tmp[ 1 ];
5244			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
5245
5246			// There *must* be a type, no attaching namespace-only handlers
5247			if ( !type ) {
5248				continue;
5249			}
5250
5251			// If event changes its type, use the special event handlers for the changed type
5252			special = jQuery.event.special[ type ] || {};
5253
5254			// If selector defined, determine special event api type, otherwise given type
5255			type = ( selector ? special.delegateType : special.bindType ) || type;
5256
5257			// Update special based on newly reset type
5258			special = jQuery.event.special[ type ] || {};
5259
5260			// handleObj is passed to all event handlers
5261			handleObj = jQuery.extend( {
5262				type: type,
5263				origType: origType,
5264				data: data,
5265				handler: handler,
5266				guid: handler.guid,
5267				selector: selector,
5268				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
5269				namespace: namespaces.join( "." )
5270			}, handleObjIn );
5271
5272			// Init the event handler queue if we're the first
5273			if ( !( handlers = events[ type ] ) ) {
5274				handlers = events[ type ] = [];
5275				handlers.delegateCount = 0;
5276
5277				// Only use addEventListener if the special events handler returns false
5278				if ( !special.setup ||
5279					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
5280
5281					if ( elem.addEventListener ) {
5282						elem.addEventListener( type, eventHandle );
5283					}
5284				}
5285			}
5286
5287			if ( special.add ) {
5288				special.add.call( elem, handleObj );
5289
5290				if ( !handleObj.handler.guid ) {
5291					handleObj.handler.guid = handler.guid;
5292				}
5293			}
5294
5295			// Add to the element's handler list, delegates in front
5296			if ( selector ) {
5297				handlers.splice( handlers.delegateCount++, 0, handleObj );
5298			} else {
5299				handlers.push( handleObj );
5300			}
5301
5302			// Keep track of which events have ever been used, for event optimization
5303			jQuery.event.global[ type ] = true;
5304		}
5305
5306	},
5307
5308	// Detach an event or set of events from an element
5309	remove: function( elem, types, handler, selector, mappedTypes ) {
5310
5311		var j, origCount, tmp,
5312			events, t, handleObj,
5313			special, handlers, type, namespaces, origType,
5314			elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
5315
5316		if ( !elemData || !( events = elemData.events ) ) {
5317			return;
5318		}
5319
vendor: 4,199 bytes, lines 5320-5458
5320		// Once for each type.namespace in types; type may be omitted
5321		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
5322		t = types.length;
5323		while ( t-- ) {
5324			tmp = rtypenamespace.exec( types[ t ] ) || [];
5325			type = origType = tmp[ 1 ];
5326			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
5327
5328			// Unbind all events (on this namespace, if provided) for the element
5329			if ( !type ) {
5330				for ( type in events ) {
5331					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
5332				}
5333				continue;
5334			}
5335
5336			special = jQuery.event.special[ type ] || {};
5337			type = ( selector ? special.delegateType : special.bindType ) || type;
5338			handlers = events[ type ] || [];
5339			tmp = tmp[ 2 ] &&
5340				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
5341
5342			// Remove matching events
5343			origCount = j = handlers.length;
5344			while ( j-- ) {
5345				handleObj = handlers[ j ];
5346
5347				if ( ( mappedTypes || origType === handleObj.origType ) &&
5348					( !handler || handler.guid === handleObj.guid ) &&
5349					( !tmp || tmp.test( handleObj.namespace ) ) &&
5350					( !selector || selector === handleObj.selector ||
5351						selector === "**" && handleObj.selector ) ) {
5352					handlers.splice( j, 1 );
5353
5354					if ( handleObj.selector ) {
5355						handlers.delegateCount--;
5356					}
5357					if ( special.remove ) {
5358						special.remove.call( elem, handleObj );
5359					}
5360				}
5361			}
5362
5363			// Remove generic event handler if we removed something and no more handlers exist
5364			// (avoids potential for endless recursion during removal of special event handlers)
5365			if ( origCount && !handlers.length ) {
5366				if ( !special.teardown ||
5367					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
5368
5369					jQuery.removeEvent( elem, type, elemData.handle );
5370				}
5371
5372				delete events[ type ];
5373			}
5374		}
5375
5376		// Remove data and the expando if it's no longer used
5377		if ( jQuery.isEmptyObject( events ) ) {
5378			dataPriv.remove( elem, "handle events" );
5379		}
5380	},
5381
5382	dispatch: function( nativeEvent ) {
5383
5384		var i, j, ret, matched, handleObj, handlerQueue,
5385			args = new Array( arguments.length ),
5386
5387			// Make a writable jQuery.Event from the native event object
5388			event = jQuery.event.fix( nativeEvent ),
5389
5390			handlers = (
5391					dataPriv.get( this, "events" ) || Object.create( null )
5392				)[ event.type ] || [],
5393			special = jQuery.event.special[ event.type ] || {};
5394
5395		// Use the fix-ed jQuery.Event rather than the (read-only) native event
5396		args[ 0 ] = event;
5397
5398		for ( i = 1; i < arguments.length; i++ ) {
5399			args[ i ] = arguments[ i ];
5400		}
5401
5402		event.delegateTarget = this;
5403
5404		// Call the preDispatch hook for the mapped type, and let it bail if desired
5405		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
5406			return;
5407		}
5408
5409		// Determine handlers
5410		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
5411
5412		// Run delegates first; they may want to stop propagation beneath us
5413		i = 0;
5414		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
5415			event.currentTarget = matched.elem;
5416
5417			j = 0;
5418			while ( ( handleObj = matched.handlers[ j++ ] ) &&
5419				!event.isImmediatePropagationStopped() ) {
5420
5421				// If the event is namespaced, then each handler is only invoked if it is
5422				// specially universal or its namespaces are a superset of the event's.
5423				if ( !event.rnamespace || handleObj.namespace === false ||
5424					event.rnamespace.test( handleObj.namespace ) ) {
5425
5426					event.handleObj = handleObj;
5427					event.data = handleObj.data;
5428
5429					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
5430						handleObj.handler ).apply( matched.elem, args );
5431
5432					if ( ret !== undefined ) {
5433						if ( ( event.result = ret ) === false ) {
5434							event.preventDefault();
5435							event.stopPropagation();
5436						}
5437					}
5438				}
5439			}
5440		}
5441
5442		// Call the postDispatch hook for the mapped type
5443		if ( special.postDispatch ) {
5444			special.postDispatch.call( this, event );
5445		}
5446
5447		return event.result;
5448	},
5449
5450	handlers: function( event, handlers ) {
5451		var i, handleObj, sel, matchedHandlers, matchedSelectors,
5452			handlerQueue = [],
5453			delegateCount = handlers.delegateCount,
5454			cur = event.target;
5455
5456		// Find delegate handlers
5457		if ( delegateCount &&
5458
vendor: 5,718 bytes, lines 5459-5644
5459			// Support: IE <=9
5460			// Black-hole SVG <use> instance trees (trac-13180)
5461			cur.nodeType &&
5462
5463			// Support: Firefox <=42
5464			// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
5465			// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
5466			// Support: IE 11 only
5467			// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
5468			!( event.type === "click" && event.button >= 1 ) ) {
5469
5470			for ( ; cur !== this; cur = cur.parentNode || this ) {
5471
5472				// Don't check non-elements (#13208)
5473				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
5474				if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
5475					matchedHandlers = [];
5476					matchedSelectors = {};
5477					for ( i = 0; i < delegateCount; i++ ) {
5478						handleObj = handlers[ i ];
5479
5480						// Don't conflict with Object.prototype properties (#13203)
5481						sel = handleObj.selector + " ";
5482
5483						if ( matchedSelectors[ sel ] === undefined ) {
5484							matchedSelectors[ sel ] = handleObj.needsContext ?
5485								jQuery( sel, this ).index( cur ) > -1 :
5486								jQuery.find( sel, this, null, [ cur ] ).length;
5487						}
5488						if ( matchedSelectors[ sel ] ) {
5489							matchedHandlers.push( handleObj );
5490						}
5491					}
5492					if ( matchedHandlers.length ) {
5493						handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
5494					}
5495				}
5496			}
5497		}
5498
5499		// Add the remaining (directly-bound) handlers
5500		cur = this;
5501		if ( delegateCount < handlers.length ) {
5502			handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
5503		}
5504
5505		return handlerQueue;
5506	},
5507
5508	addProp: function( name, hook ) {
5509		Object.defineProperty( jQuery.Event.prototype, name, {
5510			enumerable: true,
5511			configurable: true,
5512
5513			get: isFunction( hook ) ?
5514				function() {
5515					if ( this.originalEvent ) {
5516							return hook( this.originalEvent );
5517					}
5518				} :
5519				function() {
5520					if ( this.originalEvent ) {
5521							return this.originalEvent[ name ];
5522					}
5523				},
5524
5525			set: function( value ) {
5526				Object.defineProperty( this, name, {
5527					enumerable: true,
5528					configurable: true,
5529					writable: true,
5530					value: value
5531				} );
5532			}
5533		} );
5534	},
5535
5536	fix: function( originalEvent ) {
5537		return originalEvent[ jQuery.expando ] ?
5538			originalEvent :
5539			new jQuery.Event( originalEvent );
5540	},
5541
5542	special: {
5543		load: {
5544
5545			// Prevent triggered image.load events from bubbling to window.load
5546			noBubble: true
5547		},
5548		click: {
5549
5550			// Utilize native event to ensure correct state for checkable inputs
5551			setup: function( data ) {
5552
5553				// For mutual compressibility with _default, replace `this` access with a local var.
5554				// `|| data` is dead code meant only to preserve the variable through minification.
5555				var el = this || data;
5556
5557				// Claim the first handler
5558				if ( rcheckableType.test( el.type ) &&
5559					el.click && nodeName( el, "input" ) ) {
5560
5561					// dataPriv.set( el, "click", ... )
5562					leverageNative( el, "click", returnTrue );
5563				}
5564
5565				// Return false to allow normal processing in the caller
5566				return false;
5567			},
5568			trigger: function( data ) {
5569
5570				// For mutual compressibility with _default, replace `this` access with a local var.
5571				// `|| data` is dead code meant only to preserve the variable through minification.
5572				var el = this || data;
5573
5574				// Force setup before triggering a click
5575				if ( rcheckableType.test( el.type ) &&
5576					el.click && nodeName( el, "input" ) ) {
5577
5578					leverageNative( el, "click" );
5579				}
5580
5581				// Return non-false to allow normal event-path propagation
5582				return true;
5583			},
5584
5585			// For cross-browser consistency, suppress native .click() on links
5586			// Also prevent it if we're currently inside a leveraged native-event stack
5587			_default: function( event ) {
5588				var target = event.target;
5589				return rcheckableType.test( target.type ) &&
5590					target.click && nodeName( target, "input" ) &&
5591					dataPriv.get( target, "click" ) ||
5592					nodeName( target, "a" );
5593			}
5594		},
5595
5596		beforeunload: {
5597			postDispatch: function( event ) {
5598
5599				// Support: Firefox 20+
5600				// Firefox doesn't alert if the returnValue field is not set.
5601				if ( event.result !== undefined && event.originalEvent ) {
5602					event.originalEvent.returnValue = event.result;
5603				}
5604			}
5605		}
5606	}
5607};
5608
5609// Ensure the presence of an event listener that handles manually-triggered
5610// synthetic events by interrupting progress until reinvoked in response to
5611// *native* events that it fires directly, ensuring that state changes have
5612// already occurred before other listeners are invoked.
5613function leverageNative( el, type, expectSync ) {
5614
5615	// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
5616	if ( !expectSync ) {
5617		if ( dataPriv.get( el, type ) === undefined ) {
5618			jQuery.event.add( el, type, returnTrue );
5619		}
5620		return;
5621	}
5622
5623	// Register the controller as a special universal handler for all event namespaces
5624	dataPriv.set( el, type, false );
5625	jQuery.event.add( el, type, {
5626		namespace: false,
5627		handler: function( event ) {
5628			var notAsync, result,
5629				saved = dataPriv.get( this, type );
5630
5631			if ( ( event.isTrigger & 1 ) && this[ type ] ) {
5632
5633				// Interrupt processing of the outer synthetic .trigger()ed event
5634				// Saved data should be false in such cases, but might be a leftover capture object
5635				// from an async native handler (gh-4350)
5636				if ( !saved.length ) {
5637
5638					// Store arguments for use when handling the inner native event
5639					// There will always be at least one argument (an event object), so this array
5640					// will not be confused with a leftover capture object.
5641					saved = slice.call( arguments );
5642					dataPriv.set( this, type, saved );
5643
5644					// Trigger the native event and capture its result
5645					// Support: IE <=9 - 11+
5646					// focus() and blur() are asynchronous
5647					notAsync = expectSync( this, type );
5648					this[ type ]();
5649					result = dataPriv.get( this, type );
5650					if ( saved !== result || notAsync ) {
5651						dataPriv.set( this, type, false );
5652					} else {
5653						result = {};
5654					}
5655					if ( saved !== result ) {
5656
5657						// Cancel the outer synthetic event
5658						event.stopImmediatePropagation();
5659						event.preventDefault();
5660						return result.value;
5661					}
5662
5663				// If this is an inner synthetic event for an event with a bubbling surrogate
5664				// (focus or blur), assume that the surrogate already propagated from triggering the
5665				// native event and prevent that from happening again here.
5666				// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
5667				// bubbling surrogate propagates *after* the non-bubbling base), but that seems
5668				// less bad than duplication.
5669				} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
5670					event.stopPropagation();
5671				}
5672
5673			// If this is a native event triggered above, everything is now in order
5674			// Fire an inner synthetic event with the original arguments
5675			} else if ( saved.length ) {
5676
5677				// ...and capture the result
5678				dataPriv.set( this, type, {
5679					value: jQuery.event.trigger(
5680
5681						// Support: IE <=9 - 11+
5682						// Extend with the prototype to reset the above stopImmediatePropagation()
5683						jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
5684						saved.slice( 1 ),
5685						this
5686					)
5687				} );
5688
5689				// Abort handling of the native event
5690				event.stopImmediatePropagation();
5691			}
5692		}
5693	} );
5694}
5695
5696jQuery.removeEvent = function( elem, type, handle ) {
5697
5698	// This "if" is needed for plain objects
5699	if ( elem.removeEventListener ) {
5700		elem.removeEventListener( type, handle );
5701	}
5702};
5703
5704jQuery.Event = function( src, props ) {
5705
5706	// Allow instantiation without the 'new' keyword
5707	if ( !( this instanceof jQuery.Event ) ) {
5708		return new jQuery.Event( src, props );
5709	}
5710
5711	// Event object
5712	if ( src && src.type ) {
5713		this.originalEvent = src;
5714		this.type = src.type;
5715
5716		// Events bubbling up the document may have been marked as prevented
5717		// by a handler lower down the tree; reflect the correct value.
5718		this.isDefaultPrevented = src.defaultPrevented ||
5719				src.defaultPrevented === undefined &&
5720
5721				// Support: Android <=2.3 only
5722				src.returnValue === false ?
5723			returnTrue :
5724			returnFalse;
5725
5726		// Create target properties
5727		// Support: Safari <=6 - 7 only
5728		// Target should not be a text node (#504, #13143)
5729		this.target = ( src.target && src.target.nodeType === 3 ) ?
5730			src.target.parentNode :
5731			src.target;
5732
5733		this.currentTarget = src.currentTarget;
5734		this.relatedTarget = src.relatedTarget;
5735
5736	// Event type
5737	} else {
5738		this.type = src;
5739	}
5740
5741	// Put explicitly provided properties onto the event object
5742	if ( props ) {
5743		jQuery.extend( this, props );
5744	}
5745
5746	// Create a timestamp if incoming event doesn't have one
5747	this.timeStamp = src && src.timeStamp || Date.now();
5748
5749	// Mark it as fixed
5750	this[ jQuery.expando ] = true;
5751};
5752
5753// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
5754// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
5755jQuery.Event.prototype = {
5756	constructor: jQuery.Event,
5757	isDefaultPrevented: returnFalse,
5758	isPropagationStopped: returnFalse,
5759	isImmediatePropagationStopped: returnFalse,
5760	isSimulated: false,
5761
5762	preventDefault: function() {
5763		var e = this.originalEvent;
5764
5765		this.isDefaultPrevented = returnTrue;
5766
5767		if ( e && !this.isSimulated ) {
5768			e.preventDefault();
5769		}
5770	},
5771	stopPropagation: function() {
5772		var e = this.originalEvent;
5773
5774		this.isPropagationStopped = returnTrue;
5775
5776		if ( e && !this.isSimulated ) {
5777			e.stopPropagation();
5778		}
5779	},
5780	stopImmediatePropagation: function() {
5781		var e = this.originalEvent;
5782
5783		this.isImmediatePropagationStopped = returnTrue;
5784
5785		if ( e && !this.isSimulated ) {
5786			e.stopImmediatePropagation();
5787		}
5788
5789		this.stopPropagation();
5790	}
5791};
5792
5793// Includes all common event props including KeyEvent and MouseEvent specific props
5794jQuery.each( {
5795	altKey: true,
5796	bubbles: true,
5797	cancelable: true,
5798	changedTouches: true,
5799	ctrlKey: true,
5800	detail: true,
5801	eventPhase: true,
5802	metaKey: true,
5803	pageX: true,
5804	pageY: true,
5805	shiftKey: true,
5806	view: true,
5807	"char": true,
5808	code: true,
5809	charCode: true,
5810	key: true,
5811	keyCode: true,
5812	button: true,
5813	buttons: true,
5814	clientX: true,
5815	clientY: true,
5816	offsetX: true,
5817	offsetY: true,
5818	pointerId: true,
5819	pointerType: true,
5820	screenX: true,
5821	screenY: true,
5822	targetTouches: true,
5823	toElement: true,
5824	touches: true,
5825
5826	which: function( event ) {
5827		var button = event.button;
5828
5829		// Add which for key events
5830		if ( event.which == null && rkeyEvent.test( event.type ) ) {
5831			return event.charCode != null ? event.charCode : event.keyCode;
5832		}
5833
5834		// Add which for click: 1 === left; 2 === middle; 3 === right
5835		if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
5836			if ( button & 1 ) {
5837				return 1;
5838			}
5839
5840			if ( button & 2 ) {
5841				return 3;
5842			}
5843
5844			if ( button & 4 ) {
5845				return 2;
5846			}
5847
5848			return 0;
5849		}
5850
5851		return event.which;
5852	}
5853}, jQuery.event.addProp );
5854
5855jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
5856	jQuery.event.special[ type ] = {
5857
5858		// Utilize native event if possible so blur/focus sequence is correct
5859		setup: function() {
5860
5861			// Claim the first handler
5862			// dataPriv.set( this, "focus", ... )
5863			// dataPriv.set( this, "blur", ... )
5864			leverageNative( this, type, expectSync );
5865
5866			// Return false to allow normal processing in the caller
5867			return false;
5868		},
5869		trigger: function() {
5870
5871			// Force setup before trigger
5872			leverageNative( this, type );
5873
5874			// Return non-false to allow normal event-path propagation
vendor: 17,941 bytes, lines 5874-6545
5874
5875			return true;
5876		},
5877
5878		delegateType: delegateType
5879	};
5880} );
5881
5882// Create mouseenter/leave events using mouseover/out and event-time checks
5883// so that event delegation works in jQuery.
5884// Do the same for pointerenter/pointerleave and pointerover/pointerout
5885//
5886// Support: Safari 7 only
5887// Safari sends mouseenter too often; see:
5888// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
5889// for the description of the bug (it existed in older Chrome versions as well).
5890jQuery.each( {
5891	mouseenter: "mouseover",
5892	mouseleave: "mouseout",
5893	pointerenter: "pointerover",
5894	pointerleave: "pointerout"
5895}, function( orig, fix ) {
5896	jQuery.event.special[ orig ] = {
5897		delegateType: fix,
5898		bindType: fix,
5899
5900		handle: function( event ) {
5901			var ret,
5902				target = this,
5903				related = event.relatedTarget,
5904				handleObj = event.handleObj;
5905
5906			// For mouseenter/leave call the handler if related is outside the target.
5907			// NB: No relatedTarget if the mouse left/entered the browser window
5908			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
5909				event.type = handleObj.origType;
5910				ret = handleObj.handler.apply( this, arguments );
5911				event.type = fix;
5912			}
5913			return ret;
5914		}
5915	};
5916} );
5917
5918jQuery.fn.extend( {
5919
5920	on: function( types, selector, data, fn ) {
5921		return on( this, types, selector, data, fn );
5922	},
5923	one: function( types, selector, data, fn ) {
5924		return on( this, types, selector, data, fn, 1 );
5925	},
5926	off: function( types, selector, fn ) {
5927		var handleObj, type;
5928		if ( types && types.preventDefault && types.handleObj ) {
5929
5930			// ( event )  dispatched jQuery.Event
5931			handleObj = types.handleObj;
5932			jQuery( types.delegateTarget ).off(
5933				handleObj.namespace ?
5934					handleObj.origType + "." + handleObj.namespace :
5935					handleObj.origType,
5936				handleObj.selector,
5937				handleObj.handler
5938			);
5939			return this;
5940		}
5941		if ( typeof types === "object" ) {
5942
5943			// ( types-object [, selector] )
5944			for ( type in types ) {
5945				this.off( type, selector, types[ type ] );
5946			}
5947			return this;
5948		}
5949		if ( selector === false || typeof selector === "function" ) {
5950
5951			// ( types [, fn] )
5952			fn = selector;
5953			selector = undefined;
5954		}
5955		if ( fn === false ) {
5956			fn = returnFalse;
5957		}
5958		return this.each( function() {
5959			jQuery.event.remove( this, types, fn, selector );
5960		} );
5961	}
5962} );
5963
5964
5965var
5966
5967	// Support: IE <=10 - 11, Edge 12 - 13 only
5968	// In IE/Edge using regex groups here causes severe slowdowns.
5969	// See https://connect.microsoft.com/IE/feedback/details/1736512/
5970	rnoInnerhtml = /<script|<style|<link/i,
5971
5972	// checked="checked" or checked
5973	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
5974	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
5975
5976// Prefer a tbody over its parent table for containing new rows
5977function manipulationTarget( elem, content ) {
5978	if ( nodeName( elem, "table" ) &&
5979		nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
5980
5981		return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
5982	}
5983
5984	return elem;
5985}
5986
5987// Replace/restore the type attribute of script elements for safe DOM manipulation
5988function disableScript( elem ) {
5989	elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
5990	return elem;
5991}
5992function restoreScript( elem ) {
5993	if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
5994		elem.type = elem.type.slice( 5 );
5995	} else {
5996		elem.removeAttribute( "type" );
5997	}
5998
5999	return elem;
6000}
6001
6002function cloneCopyEvent( src, dest ) {
6003	var i, l, type, pdataOld, udataOld, udataCur, events;
6004
6005	if ( dest.nodeType !== 1 ) {
6006		return;
6007	}
6008
6009	// 1. Copy private data: events, handlers, etc.
6010	if ( dataPriv.hasData( src ) ) {
6011		pdataOld = dataPriv.get( src );
6012		events = pdataOld.events;
6013
6014		if ( events ) {
6015			dataPriv.remove( dest, "handle events" );
6016
6017			for ( type in events ) {
6018				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
6019					jQuery.event.add( dest, type, events[ type ][ i ] );
6020				}
6021			}
6022		}
6023	}
6024
6025	// 2. Copy user data
6026	if ( dataUser.hasData( src ) ) {
6027		udataOld = dataUser.access( src );
6028		udataCur = jQuery.extend( {}, udataOld );
6029
6030		dataUser.set( dest, udataCur );
6031	}
6032}
6033
6034// Fix IE bugs, see support tests
6035function fixInput( src, dest ) {
6036	var nodeName = dest.nodeName.toLowerCase();
6037
6038	// Fails to persist the checked state of a cloned checkbox or radio button.
6039	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
6040		dest.checked = src.checked;
6041
6042	// Fails to return the selected option to the default selected state when cloning options
6043	} else if ( nodeName === "input" || nodeName === "textarea" ) {
6044		dest.defaultValue = src.defaultValue;
6045	}
6046}
6047
6048function domManip( collection, args, callback, ignored ) {
6049
6050	// Flatten any nested arrays
6051	args = flat( args );
6052
6053	var fragment, first, scripts, hasScripts, node, doc,
6054		i = 0,
6055		l = collection.length,
6056		iNoClone = l - 1,
6057		value = args[ 0 ],
6058		valueIsFunction = isFunction( value );
6059
6060	// We can't cloneNode fragments that contain checked, in WebKit
6061	if ( valueIsFunction ||
6062			( l > 1 && typeof value === "string" &&
6063				!support.checkClone && rchecked.test( value ) ) ) {
6064		return collection.each( function( index ) {
6065			var self = collection.eq( index );
6066			if ( valueIsFunction ) {
6067				args[ 0 ] = value.call( this, index, self.html() );
6068			}
6069			domManip( self, args, callback, ignored );
6070		} );
6071	}
6072
6073	if ( l ) {
6074		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
6075		first = fragment.firstChild;
6076
6077		if ( fragment.childNodes.length === 1 ) {
6078			fragment = first;
6079		}
6080
6081		// Require either new content or an interest in ignored elements to invoke the callback
6082		if ( first || ignored ) {
6083			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
6084			hasScripts = scripts.length;
6085
6086			// Use the original fragment for the last item
6087			// instead of the first because it can end up
6088			// being emptied incorrectly in certain situations (#8070).
6089			for ( ; i < l; i++ ) {
6090				node = fragment;
6091
6092				if ( i !== iNoClone ) {
6093					node = jQuery.clone( node, true, true );
6094
6095					// Keep references to cloned scripts for later restoration
6096					if ( hasScripts ) {
6097
6098						// Support: Android <=4.0 only, PhantomJS 1 only
6099						// push.apply(_, arraylike) throws on ancient WebKit
6100						jQuery.merge( scripts, getAll( node, "script" ) );
6101					}
6102				}
6103
6104				callback.call( collection[ i ], node, i );
6105			}
6106
6107			if ( hasScripts ) {
6108				doc = scripts[ scripts.length - 1 ].ownerDocument;
6109
6110				// Reenable scripts
6111				jQuery.map( scripts, restoreScript );
6112
6113				// Evaluate executable scripts on first document insertion
6114				for ( i = 0; i < hasScripts; i++ ) {
6115					node = scripts[ i ];
6116					if ( rscriptType.test( node.type || "" ) &&
6117						!dataPriv.access( node, "globalEval" ) &&
6118						jQuery.contains( doc, node ) ) {
6119
6120						if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {
6121
6122							// Optional AJAX dependency, but won't run scripts if not present
6123							if ( jQuery._evalUrl && !node.noModule ) {
6124								jQuery._evalUrl( node.src, {
6125									nonce: node.nonce || node.getAttribute( "nonce" )
6126								}, doc );
6127							}
6128						} else {
6129							DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
6130						}
6131					}
6132				}
6133			}
6134		}
6135	}
6136
6137	return collection;
6138}
6139
6140function remove( elem, selector, keepData ) {
6141	var node,
6142		nodes = selector ? jQuery.filter( selector, elem ) : elem,
6143		i = 0;
6144
6145	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
6146		if ( !keepData && node.nodeType === 1 ) {
6147			jQuery.cleanData( getAll( node ) );
6148		}
6149
6150		if ( node.parentNode ) {
6151			if ( keepData && isAttached( node ) ) {
6152				setGlobalEval( getAll( node, "script" ) );
6153			}
6154			node.parentNode.removeChild( node );
6155		}
6156	}
6157
6158	return elem;
6159}
6160
6161jQuery.extend( {
6162	htmlPrefilter: function( html ) {
6163		return html;
6164	},
6165
6166	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
6167		var i, l, srcElements, destElements,
6168			clone = elem.cloneNode( true ),
6169			inPage = isAttached( elem );
6170
6171		// Fix IE cloning issues
6172		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
6173				!jQuery.isXMLDoc( elem ) ) {
6174
6175			// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
6176			destElements = getAll( clone );
6177			srcElements = getAll( elem );
6178
6179			for ( i = 0, l = srcElements.length; i < l; i++ ) {
6180				fixInput( srcElements[ i ], destElements[ i ] );
6181			}
6182		}
6183
6184		// Copy the events from the original to the clone
6185		if ( dataAndEvents ) {
6186			if ( deepDataAndEvents ) {
6187				srcElements = srcElements || getAll( elem );
6188				destElements = destElements || getAll( clone );
6189
6190				for ( i = 0, l = srcElements.length; i < l; i++ ) {
6191					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
6192				}
6193			} else {
6194				cloneCopyEvent( elem, clone );
6195			}
6196		}
6197
6198		// Preserve script evaluation history
6199		destElements = getAll( clone, "script" );
6200		if ( destElements.length > 0 ) {
6201			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
6202		}
6203
6204		// Return the cloned set
6205		return clone;
6206	},
6207
6208	cleanData: function( elems ) {
6209		var data, elem, type,
6210			special = jQuery.event.special,
6211			i = 0;
6212
6213		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
6214			if ( acceptData( elem ) ) {
6215				if ( ( data = elem[ dataPriv.expando ] ) ) {
6216					if ( data.events ) {
6217						for ( type in data.events ) {
6218							if ( special[ type ] ) {
6219								jQuery.event.remove( elem, type );
6220
6221							// This is a shortcut to avoid jQuery.event.remove's overhead
6222							} else {
6223								jQuery.removeEvent( elem, type, data.handle );
6224							}
6225						}
6226					}
6227
6228					// Support: Chrome <=35 - 45+
6229					// Assign undefined instead of using delete, see Data#remove
6230					elem[ dataPriv.expando ] = undefined;
6231				}
6232				if ( elem[ dataUser.expando ] ) {
6233
6234					// Support: Chrome <=35 - 45+
6235					// Assign undefined instead of using delete, see Data#remove
6236					elem[ dataUser.expando ] = undefined;
6237				}
6238			}
6239		}
6240	}
6241} );
6242
6243jQuery.fn.extend( {
6244	detach: function( selector ) {
6245		return remove( this, selector, true );
6246	},
6247
6248	remove: function( selector ) {
6249		return remove( this, selector );
6250	},
6251
6252	text: function( value ) {
6253		return access( this, function( value ) {
6254			return value === undefined ?
6255				jQuery.text( this ) :
6256				this.empty().each( function() {
6257					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
6258						this.textContent = value;
6259					}
6260				} );
6261		}, null, value, arguments.length );
6262	},
6263
6264	append: function() {
6265		return domManip( this, arguments, function( elem ) {
6266			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
6267				var target = manipulationTarget( this, elem );
6268				target.appendChild( elem );
6269			}
6270		} );
6271	},
6272
6273	prepend: function() {
6274		return domManip( this, arguments, function( elem ) {
6275			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
6276				var target = manipulationTarget( this, elem );
6277				target.insertBefore( elem, target.firstChild );
6278			}
6279		} );
6280	},
6281
6282	before: function() {
6283		return domManip( this, arguments, function( elem ) {
6284			if ( this.parentNode ) {
6285				this.parentNode.insertBefore( elem, this );
6286			}
6287		} );
6288	},
6289
6290	after: function() {
6291		return domManip( this, arguments, function( elem ) {
6292			if ( this.parentNode ) {
6293				this.parentNode.insertBefore( elem, this.nextSibling );
6294			}
6295		} );
6296	},
6297
6298	empty: function() {
6299		var elem,
6300			i = 0;
6301
6302		for ( ; ( elem = this[ i ] ) != null; i++ ) {
6303			if ( elem.nodeType === 1 ) {
6304
6305				// Prevent memory leaks
6306				jQuery.cleanData( getAll( elem, false ) );
6307
6308				// Remove any remaining nodes
6309				elem.textContent = "";
6310			}
6311		}
6312
6313		return this;
6314	},
6315
6316	clone: function( dataAndEvents, deepDataAndEvents ) {
6317		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
6318		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
6319
6320		return this.map( function() {
6321			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
6322		} );
6323	},
6324
6325	html: function( value ) {
6326		return access( this, function( value ) {
6327			var elem = this[ 0 ] || {},
6328				i = 0,
6329				l = this.length;
6330
6331			if ( value === undefined && elem.nodeType === 1 ) {
6332				return elem.innerHTML;
6333			}
6334
6335			// See if we can take a shortcut and just use innerHTML
6336			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
6337				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
6338
6339				value = jQuery.htmlPrefilter( value );
6340
6341				try {
6342					for ( ; i < l; i++ ) {
6343						elem = this[ i ] || {};
6344
6345						// Remove element nodes and prevent memory leaks
6346						if ( elem.nodeType === 1 ) {
6347							jQuery.cleanData( getAll( elem, false ) );
6348							elem.innerHTML = value;
6349						}
6350					}
6351
6352					elem = 0;
6353
6354				// If using innerHTML throws an exception, use the fallback method
6355				} catch ( e ) {}
6356			}
6357
6358			if ( elem ) {
6359				this.empty().append( value );
6360			}
6361		}, null, value, arguments.length );
6362	},
6363
6364	replaceWith: function() {
6365		var ignored = [];
6366
6367		// Make the changes, replacing each non-ignored context element with the new content
6368		return domManip( this, arguments, function( elem ) {
6369			var parent = this.parentNode;
6370
6371			if ( jQuery.inArray( this, ignored ) < 0 ) {
6372				jQuery.cleanData( getAll( this ) );
6373				if ( parent ) {
6374					parent.replaceChild( elem, this );
6375				}
6376			}
6377
6378		// Force callback invocation
6379		}, ignored );
6380	}
6381} );
6382
6383jQuery.each( {
6384	appendTo: "append",
6385	prependTo: "prepend",
6386	insertBefore: "before",
6387	insertAfter: "after",
6388	replaceAll: "replaceWith"
6389}, function( name, original ) {
6390	jQuery.fn[ name ] = function( selector ) {
6391		var elems,
6392			ret = [],
6393			insert = jQuery( selector ),
6394			last = insert.length - 1,
6395			i = 0;
6396
6397		for ( ; i <= last; i++ ) {
6398			elems = i === last ? this : this.clone( true );
6399			jQuery( insert[ i ] )[ original ]( elems );
6400
6401			// Support: Android <=4.0 only, PhantomJS 1 only
6402			// .get() because push.apply(_, arraylike) throws on ancient WebKit
6403			push.apply( ret, elems.get() );
6404		}
6405
6406		return this.pushStack( ret );
6407	};
6408} );
6409var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
6410
6411var getStyles = function( elem ) {
6412
6413		// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
6414		// IE throws on elements created in popups
6415		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
6416		var view = elem.ownerDocument.defaultView;
6417
6418		if ( !view || !view.opener ) {
6419			view = window;
6420		}
6421
6422		return view.getComputedStyle( elem );
6423	};
6424
6425var swap = function( elem, options, callback ) {
6426	var ret, name,
6427		old = {};
6428
6429	// Remember the old values, and insert the new ones
6430	for ( name in options ) {
6431		old[ name ] = elem.style[ name ];
6432		elem.style[ name ] = options[ name ];
6433	}
6434
6435	ret = callback.call( elem );
6436
6437	// Revert the old values
6438	for ( name in options ) {
6439		elem.style[ name ] = old[ name ];
6440	}
6441
6442	return ret;
6443};
6444
6445
6446var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
6447
6448
6449
6450( function() {
6451
6452	// Executing both pixelPosition & boxSizingReliable tests require only one layout
6453	// so they're executed at the same time to save the second computation.
6454	function computeStyleTests() {
6455
6456		// This is a singleton, we need to execute it only once
6457		if ( !div ) {
6458			return;
6459		}
6460
6461		container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
6462			"margin-top:1px;padding:0;border:0";
6463		div.style.cssText =
6464			"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
6465			"margin:auto;border:1px;padding:1px;" +
6466			"width:60%;top:1%";
6467		documentElement.appendChild( container ).appendChild( div );
6468
6469		var divStyle = window.getComputedStyle( div );
6470		pixelPositionVal = divStyle.top !== "1%";
6471
6472		// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
6473		reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
6474
6475		// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
6476		// Some styles come back with percentage values, even though they shouldn't
6477		div.style.right = "60%";
6478		pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
6479
6480		// Support: IE 9 - 11 only
6481		// Detect misreporting of content dimensions for box-sizing:border-box elements
6482		boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
6483
6484		// Support: IE 9 only
6485		// Detect overflow:scroll screwiness (gh-3699)
6486		// Support: Chrome <=64
6487		// Don't get tricked when zoom affects offsetWidth (gh-4029)
6488		div.style.position = "absolute";
6489		scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;
6490
6491		documentElement.removeChild( container );
6492
6493		// Nullify the div so it wouldn't be stored in the memory and
6494		// it will also be a sign that checks already performed
6495		div = null;
6496	}
6497
6498	function roundPixelMeasures( measure ) {
6499		return Math.round( parseFloat( measure ) );
6500	}
6501
6502	var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
6503		reliableTrDimensionsVal, reliableMarginLeftVal,
6504		container = document.createElement( "div" ),
6505		div = document.createElement( "div" );
6506
6507	// Finish early in limited (non-browser) environments
6508	if ( !div.style ) {
6509		return;
6510	}
6511
6512	// Support: IE <=9 - 11 only
6513	// Style of cloned element affects source element cloned (#8908)
6514	div.style.backgroundClip = "content-box";
6515	div.cloneNode( true ).style.backgroundClip = "";
6516	support.clearCloneStyle = div.style.backgroundClip === "content-box";
6517
6518	jQuery.extend( support, {
6519		boxSizingReliable: function() {
6520			computeStyleTests();
6521			return boxSizingReliableVal;
6522		},
6523		pixelBoxStyles: function() {
6524			computeStyleTests();
6525			return pixelBoxStylesVal;
6526		},
6527		pixelPosition: function() {
6528			computeStyleTests();
6529			return pixelPositionVal;
6530		},
6531		reliableMarginLeft: function() {
6532			computeStyleTests();
6533			return reliableMarginLeftVal;
6534		},
6535		scrollboxSize: function() {
6536			computeStyleTests();
6537			return scrollboxSizeVal;
6538		},
6539
6540		// Support: IE 9 - 11+, Edge 15 - 18+
6541		// IE/Edge misreport `getComputedStyle` of table rows with width/height
6542		// set in CSS while `offset*` properties report correct values.
6543		// Behavior in IE 9 is more subtle than in newer versions & it passes
6544		// some versions of this test; make sure not to make it pass there!
6545		
6545reliableTrDimensions: function() {
6546			var table, tr, trChild, trStyle;
6547			if ( reliableTrDimensionsVal == null ) {
6548				table = document.createElement( "table" );
6549				tr = document.createElement( "tr" );
6550				trChild = document.createElement( "div" );
6551
6552				table.style.cssText = "position:absolute;left:-11111px";
6553				tr.style.height = "1px";
6554				trChild.style.height = "9px";
6555
6556				documentElement
6557					.appendChild( table )
6558					.appendChild( tr )
6559					.appendChild( trChild );
6560
6561				trStyle = window.getComputedStyle( tr );
6562				reliableTrDimensionsVal = parseInt( trStyle.height ) > 3
vendor: 13,297 bytes, lines 6562-7026
6562;
6563
6564				documentElement.removeChild( table );
6565			}
6566			return reliableTrDimensionsVal;
6567		}
6568	} );
6569} )();
6570
6571
6572function curCSS( elem, name, computed ) {
6573	var width, minWidth, maxWidth, ret,
6574
6575		// Support: Firefox 51+
6576		// Retrieving style before computed somehow
6577		// fixes an issue with getting wrong values
6578		// on detached elements
6579		style = elem.style;
6580
6581	computed = computed || getStyles( elem );
6582
6583	// getPropertyValue is needed for:
6584	//   .css('filter') (IE 9 only, #12537)
6585	//   .css('--customProperty) (#3144)
6586	if ( computed ) {
6587		ret = computed.getPropertyValue( name ) || computed[ name ];
6588
6589		if ( ret === "" && !isAttached( elem ) ) {
6590			ret = jQuery.style( elem, name );
6591		}
6592
6593		// A tribute to the "awesome hack by Dean Edwards"
6594		// Android Browser returns percentage for some values,
6595		// but width seems to be reliably pixels.
6596		// This is against the CSSOM draft spec:
6597		// https://drafts.csswg.org/cssom/#resolved-values
6598		if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
6599
6600			// Remember the original values
6601			width = style.width;
6602			minWidth = style.minWidth;
6603			maxWidth = style.maxWidth;
6604
6605			// Put in the new values to get a computed value out
6606			style.minWidth = style.maxWidth = style.width = ret;
6607			ret = computed.width;
6608
6609			// Revert the changed values
6610			style.width = width;
6611			style.minWidth = minWidth;
6612			style.maxWidth = maxWidth;
6613		}
6614	}
6615
6616	return ret !== undefined ?
6617
6618		// Support: IE <=9 - 11 only
6619		// IE returns zIndex value as an integer.
6620		ret + "" :
6621		ret;
6622}
6623
6624
6625function addGetHookIf( conditionFn, hookFn ) {
6626
6627	// Define the hook, we'll check on the first run if it's really needed.
6628	return {
6629		get: function() {
6630			if ( conditionFn() ) {
6631
6632				// Hook not needed (or it's not possible to use it due
6633				// to missing dependency), remove it.
6634				delete this.get;
6635				return;
6636			}
6637
6638			// Hook needed; redefine it so that the support test is not executed again.
6639			return ( this.get = hookFn ).apply( this, arguments );
6640		}
6641	};
6642}
6643
6644
6645var cssPrefixes = [ "Webkit", "Moz", "ms" ],
6646	emptyStyle = document.createElement( "div" ).style,
6647	vendorProps = {};
6648
6649// Return a vendor-prefixed property or undefined
6650function vendorPropName( name ) {
6651
6652	// Check for vendor prefixed names
6653	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
6654		i = cssPrefixes.length;
6655
6656	while ( i-- ) {
6657		name = cssPrefixes[ i ] + capName;
6658		if ( name in emptyStyle ) {
6659			return name;
6660		}
6661	}
6662}
6663
6664// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
6665function finalPropName( name ) {
6666	var final = jQuery.cssProps[ name ] || vendorProps[ name ];
6667
6668	if ( final ) {
6669		return final;
6670	}
6671	if ( name in emptyStyle ) {
6672		return name;
6673	}
6674	return vendorProps[ name ] = vendorPropName( name ) || name;
6675}
6676
6677
6678var
6679
6680	// Swappable if display is none or starts with table
6681	// except "table", "table-cell", or "table-caption"
6682	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
6683	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
6684	rcustomProp = /^--/,
6685	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
6686	cssNormalTransform = {
6687		letterSpacing: "0",
6688		fontWeight: "400"
6689	};
6690
6691function setPositiveNumber( _elem, value, subtract ) {
6692
6693	// Any relative (+/-) values have already been
6694	// normalized at this point
6695	var matches = rcssNum.exec( value );
6696	return matches ?
6697
6698		// Guard against undefined "subtract", e.g., when used as in cssHooks
6699		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
6700		value;
6701}
6702
6703function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
6704	var i = dimension === "width" ? 1 : 0,
6705		extra = 0,
6706		delta = 0;
6707
6708	// Adjustment may not be necessary
6709	if ( box === ( isBorderBox ? "border" : "content" ) ) {
6710		return 0;
6711	}
6712
6713	for ( ; i < 4; i += 2 ) {
6714
6715		// Both box models exclude margin
6716		if ( box === "margin" ) {
6717			delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
6718		}
6719
6720		// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
6721		if ( !isBorderBox ) {
6722
6723			// Add padding
6724			delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
6725
6726			// For "border" or "margin", add border
6727			if ( box !== "padding" ) {
6728				delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
6729
6730			// But still keep track of it otherwise
6731			} else {
6732				extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
6733			}
6734
6735		// If we get here with a border-box (content + padding + border), we're seeking "content" or
6736		// "padding" or "margin"
6737		} else {
6738
6739			// For "content", subtract padding
6740			if ( box === "content" ) {
6741				delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
6742			}
6743
6744			// For "content" or "padding", subtract border
6745			if ( box !== "margin" ) {
6746				delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
6747			}
6748		}
6749	}
6750
6751	// Account for positive content-box scroll gutter when requested by providing computedVal
6752	if ( !isBorderBox && computedVal >= 0 ) {
6753
6754		// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
6755		// Assuming integer scroll gutter, subtract the rest and round down
6756		delta += Math.max( 0, Math.ceil(
6757			elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
6758			computedVal -
6759			delta -
6760			extra -
6761			0.5
6762
6763		// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
6764		// Use an explicit zero to avoid NaN (gh-3964)
6765		) ) || 0;
6766	}
6767
6768	return delta;
6769}
6770
6771function getWidthOrHeight( elem, dimension, extra ) {
6772
6773	// Start with computed style
6774	var styles = getStyles( elem ),
6775
6776		// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
6777		// Fake content-box until we know it's needed to know the true value.
6778		boxSizingNeeded = !support.boxSizingReliable() || extra,
6779		isBorderBox = boxSizingNeeded &&
6780			jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
6781		valueIsBorderBox = isBorderBox,
6782
6783		val = curCSS( elem, dimension, styles ),
6784		offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
6785
6786	// Support: Firefox <=54
6787	// Return a confounding non-pixel value or feign ignorance, as appropriate.
6788	if ( rnumnonpx.test( val ) ) {
6789		if ( !extra ) {
6790			return val;
6791		}
6792		val = "auto";
6793	}
6794
6795
6796	// Support: IE 9 - 11 only
6797	// Use offsetWidth/offsetHeight for when box sizing is unreliable.
6798	// In those cases, the computed value can be trusted to be border-box.
6799	if ( ( !support.boxSizingReliable() && isBorderBox ||
6800
6801		// Support: IE 10 - 11+, Edge 15 - 18+
6802		// IE/Edge misreport `getComputedStyle` of table rows with width/height
6803		// set in CSS while `offset*` properties report correct values.
6804		// Interestingly, in some cases IE 9 doesn't suffer from this issue.
6805		!support.reliableTrDimensions() && nodeName( elem, "tr" ) ||
6806
6807		// Fall back to offsetWidth/offsetHeight when value is "auto"
6808		// This happens for inline elements with no explicit setting (gh-3571)
6809		val === "auto" ||
6810
6811		// Support: Android <=4.1 - 4.3 only
6812		// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
6813		!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
6814
6815		// Make sure the element is visible & connected
6816		elem.getClientRects().length ) {
6817
6818		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
6819
6820		// Where available, offsetWidth/offsetHeight approximate border box dimensions.
6821		// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
6822		// retrieved value as a content box dimension.
6823		valueIsBorderBox = offsetProp in elem;
6824		if ( valueIsBorderBox ) {
6825			val = elem[ offsetProp ];
6826		}
6827	}
6828
6829	// Normalize "" and auto
6830	val = parseFloat( val ) || 0;
6831
6832	// Adjust for the element's box model
6833	return ( val +
6834		boxModelAdjustment(
6835			elem,
6836			dimension,
6837			extra || ( isBorderBox ? "border" : "content" ),
6838			valueIsBorderBox,
6839			styles,
6840
6841			// Provide the current computed size to request scroll gutter calculation (gh-3589)
6842			val
6843		)
6844	) + "px";
6845}
6846
6847jQuery.extend( {
6848
6849	// Add in style property hooks for overriding the default
6850	// behavior of getting and setting a style property
6851	cssHooks: {
6852		opacity: {
6853			get: function( elem, computed ) {
6854				if ( computed ) {
6855
6856					// We should always get a number back from opacity
6857					var ret = curCSS( elem, "opacity" );
6858					return ret === "" ? "1" : ret;
6859				}
6860			}
6861		}
6862	},
6863
6864	// Don't automatically add "px" to these possibly-unitless properties
6865	cssNumber: {
6866		"animationIterationCount": true,
6867		"columnCount": true,
6868		"fillOpacity": true,
6869		"flexGrow": true,
6870		"flexShrink": true,
6871		"fontWeight": true,
6872		"gridArea": true,
6873		"gridColumn": true,
6874		"gridColumnEnd": true,
6875		"gridColumnStart": true,
6876		"gridRow": true,
6877		"gridRowEnd": true,
6878		"gridRowStart": true,
6879		"lineHeight": true,
6880		"opacity": true,
6881		"order": true,
6882		"orphans": true,
6883		"widows": true,
6884		"zIndex": true,
6885		"zoom": true
6886	},
6887
6888	// Add in properties whose names you wish to fix before
6889	// setting or getting the value
6890	cssProps: {},
6891
6892	// Get and set the style property on a DOM Node
6893	style: function( elem, name, value, extra ) {
6894
6895		// Don't set styles on text and comment nodes
6896		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
6897			return;
6898		}
6899
6900		// Make sure that we're working with the right name
6901		var ret, type, hooks,
6902			origName = camelCase( name ),
6903			isCustomProp = rcustomProp.test( name ),
6904			style = elem.style;
6905
6906		// Make sure that we're working with the right name. We don't
6907		// want to query the value if it is a CSS custom property
6908		// since they are user-defined.
6909		if ( !isCustomProp ) {
6910			name = finalPropName( origName );
6911		}
6912
6913		// Gets hook for the prefixed version, then unprefixed version
6914		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
6915
6916		// Check if we're setting a value
6917		if ( value !== undefined ) {
6918			type = typeof value;
6919
6920			// Convert "+=" or "-=" to relative numbers (#7345)
6921			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
6922				value = adjustCSS( elem, name, ret );
6923
6924				// Fixes bug #9237
6925				type = "number";
6926			}
6927
6928			// Make sure that null and NaN values aren't set (#7116)
6929			if ( value == null || value !== value ) {
6930				return;
6931			}
6932
6933			// If a number was passed in, add the unit (except for certain CSS properties)
6934			// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
6935			// "px" to a few hardcoded values.
6936			if ( type === "number" && !isCustomProp ) {
6937				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
6938			}
6939
6940			// background-* props affect original clone's values
6941			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
6942				style[ name ] = "inherit";
6943			}
6944
6945			// If a hook was provided, use that value, otherwise just set the specified value
6946			if ( !hooks || !( "set" in hooks ) ||
6947				( value = hooks.set( elem, value, extra ) ) !== undefined ) {
6948
6949				if ( isCustomProp ) {
6950					style.setProperty( name, value );
6951				} else {
6952					style[ name ] = value;
6953				}
6954			}
6955
6956		} else {
6957
6958			// If a hook was provided get the non-computed value from there
6959			if ( hooks && "get" in hooks &&
6960				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
6961
6962				return ret;
6963			}
6964
6965			// Otherwise just get the value from the style object
6966			return style[ name ];
6967		}
6968	},
6969
6970	css: function( elem, name, extra, styles ) {
6971		var val, num, hooks,
6972			origName = camelCase( name ),
6973			isCustomProp = rcustomProp.test( name );
6974
6975		// Make sure that we're working with the right name. We don't
6976		// want to modify the value if it is a CSS custom property
6977		// since they are user-defined.
6978		if ( !isCustomProp ) {
6979			name = finalPropName( origName );
6980		}
6981
6982		// Try prefixed name followed by the unprefixed name
6983		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
6984
6985		// If a hook was provided get the computed value from there
6986		if ( hooks && "get" in hooks ) {
6987			val = hooks.get( elem, true, extra );
6988		}
6989
6990		// Otherwise, if a way to get the computed value exists, use that
6991		if ( val === undefined ) {
6992			val = curCSS( elem, name, styles );
6993		}
6994
6995		// Convert "normal" to computed value
6996		if ( val === "normal" && name in cssNormalTransform ) {
6997			val = cssNormalTransform[ name ];
6998		}
6999
7000		// Make numeric if forced or a qualifier was provided and val looks numeric
7001		if ( extra === "" || extra ) {
7002			num = parseFloat( val );
7003			return extra === true || isFinite( num ) ? num || 0 : val;
7004		}
7005
7006		return val;
7007	}
7008} );
7009
7010jQuery.each( [ "height", "width" ], function( _i, dimension ) {
7011	jQuery.cssHooks[ dimension ] = {
7012		get: function( elem, computed, extra ) {
7013			if ( computed ) {
7014
7015				// Certain elements can have dimension info if we invisibly show them
7016				// but it must have a current display style that would benefit
7017				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
7018
7019					// Support: Safari 8+
7020					// Table columns in Safari have non-zero offsetWidth & zero
7021					// getBoundingClientRect().width unless display is changed.
7022					// Support: IE <=11 only
7023					// Running getBoundingClientRect on a disconnected node
7024					// in IE throws an error.
7025					( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
7026					
vendor: 5,577 bytes, lines 7026-7228
7026	swap( elem, cssShow, function() {
7027							return getWidthOrHeight( elem, dimension, extra );
7028						} ) :
7029						getWidthOrHeight( elem, dimension, extra );
7030			}
7031		},
7032
7033		set: function( elem, value, extra ) {
7034			var matches,
7035				styles = getStyles( elem ),
7036
7037				// Only read styles.position if the test has a chance to fail
7038				// to avoid forcing a reflow.
7039				scrollboxSizeBuggy = !support.scrollboxSize() &&
7040					styles.position === "absolute",
7041
7042				// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
7043				boxSizingNeeded = scrollboxSizeBuggy || extra,
7044				isBorderBox = boxSizingNeeded &&
7045					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
7046				subtract = extra ?
7047					boxModelAdjustment(
7048						elem,
7049						dimension,
7050						extra,
7051						isBorderBox,
7052						styles
7053					) :
7054					0;
7055
7056			// Account for unreliable border-box dimensions by comparing offset* to computed and
7057			// faking a content-box to get border and padding (gh-3699)
7058			if ( isBorderBox && scrollboxSizeBuggy ) {
7059				subtract -= Math.ceil(
7060					elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
7061					parseFloat( styles[ dimension ] ) -
7062					boxModelAdjustment( elem, dimension, "border", false, styles ) -
7063					0.5
7064				);
7065			}
7066
7067			// Convert to pixels if value adjustment is needed
7068			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
7069				( matches[ 3 ] || "px" ) !== "px" ) {
7070
7071				elem.style[ dimension ] = value;
7072				value = jQuery.css( elem, dimension );
7073			}
7074
7075			return setPositiveNumber( elem, value, subtract );
7076		}
7077	};
7078} );
7079
7080jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
7081	function( elem, computed ) {
7082		if ( computed ) {
7083			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
7084				elem.getBoundingClientRect().left -
7085					swap( elem, { marginLeft: 0 }, function() {
7086						return elem.getBoundingClientRect().left;
7087					} )
7088				) + "px";
7089		}
7090	}
7091);
7092
7093// These hooks are used by animate to expand properties
7094jQuery.each( {
7095	margin: "",
7096	padding: "",
7097	border: "Width"
7098}, function( prefix, suffix ) {
7099	jQuery.cssHooks[ prefix + suffix ] = {
7100		expand: function( value ) {
7101			var i = 0,
7102				expanded = {},
7103
7104				// Assumes a single number if not a string
7105				parts = typeof value === "string" ? value.split( " " ) : [ value ];
7106
7107			for ( ; i < 4; i++ ) {
7108				expanded[ prefix + cssExpand[ i ] + suffix ] =
7109					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
7110			}
7111
7112			return expanded;
7113		}
7114	};
7115
7116	if ( prefix !== "margin" ) {
7117		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
7118	}
7119} );
7120
7121jQuery.fn.extend( {
7122	css: function( name, value ) {
7123		return access( this, function( elem, name, value ) {
7124			var styles, len,
7125				map = {},
7126				i = 0;
7127
7128			if ( Array.isArray( name ) ) {
7129				styles = getStyles( elem );
7130				len = name.length;
7131
7132				for ( ; i < len; i++ ) {
7133					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
7134				}
7135
7136				return map;
7137			}
7138
7139			return value !== undefined ?
7140				jQuery.style( elem, name, value ) :
7141				jQuery.css( elem, name );
7142		}, name, value, arguments.length > 1 );
7143	}
7144} );
7145
7146
7147function Tween( elem, options, prop, end, easing ) {
7148	return new Tween.prototype.init( elem, options, prop, end, easing );
7149}
7150jQuery.Tween = Tween;
7151
7152Tween.prototype = {
7153	constructor: Tween,
7154	init: function( elem, options, prop, end, easing, unit ) {
7155		this.elem = elem;
7156		this.prop = prop;
7157		this.easing = easing || jQuery.easing._default;
7158		this.options = options;
7159		this.start = this.now = this.cur();
7160		this.end = end;
7161		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
7162	},
7163	cur: function() {
7164		var hooks = Tween.propHooks[ this.prop ];
7165
7166		return hooks && hooks.get ?
7167			hooks.get( this ) :
7168			Tween.propHooks._default.get( this );
7169	},
7170	run: function( percent ) {
7171		var eased,
7172			hooks = Tween.propHooks[ this.prop ];
7173
7174		if ( this.options.duration ) {
7175			this.pos = eased = jQuery.easing[ this.easing ](
7176				percent, this.options.duration * percent, 0, 1, this.options.duration
7177			);
7178		} else {
7179			this.pos = eased = percent;
7180		}
7181		this.now = ( this.end - this.start ) * eased + this.start;
7182
7183		if ( this.options.step ) {
7184			this.options.step.call( this.elem, this.now, this );
7185		}
7186
7187		if ( hooks && hooks.set ) {
7188			hooks.set( this );
7189		} else {
7190			Tween.propHooks._default.set( this );
7191		}
7192		return this;
7193	}
7194};
7195
7196Tween.prototype.init.prototype = Tween.prototype;
7197
7198Tween.propHooks = {
7199	_default: {
7200		get: function( tween ) {
7201			var result;
7202
7203			// Use a property on the element directly when it is not a DOM element,
7204			// or when there is no matching style property that exists.
7205			if ( tween.elem.nodeType !== 1 ||
7206				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
7207				return tween.elem[ tween.prop ];
7208			}
7209
7210			// Passing an empty string as a 3rd parameter to .css will automatically
7211			// attempt a parseFloat and fallback to a string if the parse fails.
7212			// Simple values such as "10px" are parsed to Float;
7213			// complex values such as "rotate(1rad)" are returned as-is.
7214			result = jQuery.css( tween.elem, tween.prop, "" );
7215
7216			// Empty strings, null, undefined and "auto" are converted to 0.
7217			return !result || result === "auto" ? 0 : result;
7218		},
7219		set: function( tween ) {
7220
7221			// Use step hook for back compat.
7222			// Use cssHook if its there.
7223			// Use .style if available and use plain properties where available.
7224			if ( jQuery.fx.step[ tween.prop ] ) {
7225				jQuery.fx.step[ tween.prop ]( tween );
7226			} else if ( tween.elem.nodeType === 1 && (
7227					jQuery.cssHooks[ tween.prop ] ||
7228					tween.elem.style[ finalPropName( tween.prop ) ] != null
vendor: 14,002 bytes, lines 7228-7783
7228 ) ) {
7229				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
7230			} else {
7231				tween.elem[ tween.prop ] = tween.now;
7232			}
7233		}
7234	}
7235};
7236
7237// Support: IE <=9 only
7238// Panic based approach to setting things on disconnected nodes
7239Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
7240	set: function( tween ) {
7241		if ( tween.elem.nodeType && tween.elem.parentNode ) {
7242			tween.elem[ tween.prop ] = tween.now;
7243		}
7244	}
7245};
7246
7247jQuery.easing = {
7248	linear: function( p ) {
7249		return p;
7250	},
7251	swing: function( p ) {
7252		return 0.5 - Math.cos( p * Math.PI ) / 2;
7253	},
7254	_default: "swing"
7255};
7256
7257jQuery.fx = Tween.prototype.init;
7258
7259// Back compat <1.8 extension point
7260jQuery.fx.step = {};
7261
7262
7263
7264
7265var
7266	fxNow, inProgress,
7267	rfxtypes = /^(?:toggle|show|hide)$/,
7268	rrun = /queueHooks$/;
7269
7270function schedule() {
7271	if ( inProgress ) {
7272		if ( document.hidden === false && window.requestAnimationFrame ) {
7273			window.requestAnimationFrame( schedule );
7274		} else {
7275			window.setTimeout( schedule, jQuery.fx.interval );
7276		}
7277
7278		jQuery.fx.tick();
7279	}
7280}
7281
7282// Animations created synchronously will run synchronously
7283function createFxNow() {
7284	window.setTimeout( function() {
7285		fxNow = undefined;
7286	} );
7287	return ( fxNow = Date.now() );
7288}
7289
7290// Generate parameters to create a standard animation
7291function genFx( type, includeWidth ) {
7292	var which,
7293		i = 0,
7294		attrs = { height: type };
7295
7296	// If we include width, step value is 1 to do all cssExpand values,
7297	// otherwise step value is 2 to skip over Left and Right
7298	includeWidth = includeWidth ? 1 : 0;
7299	for ( ; i < 4; i += 2 - includeWidth ) {
7300		which = cssExpand[ i ];
7301		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
7302	}
7303
7304	if ( includeWidth ) {
7305		attrs.opacity = attrs.width = type;
7306	}
7307
7308	return attrs;
7309}
7310
7311function createTween( value, prop, animation ) {
7312	var tween,
7313		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
7314		index = 0,
7315		length = collection.length;
7316	for ( ; index < length; index++ ) {
7317		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
7318
7319			// We're done with this property
7320			return tween;
7321		}
7322	}
7323}
7324
7325function defaultPrefilter( elem, props, opts ) {
7326	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
7327		isBox = "width" in props || "height" in props,
7328		anim = this,
7329		orig = {},
7330		style = elem.style,
7331		hidden = elem.nodeType && isHiddenWithinTree( elem ),
7332		dataShow = dataPriv.get( elem, "fxshow" );
7333
7334	// Queue-skipping animations hijack the fx hooks
7335	if ( !opts.queue ) {
7336		hooks = jQuery._queueHooks( elem, "fx" );
7337		if ( hooks.unqueued == null ) {
7338			hooks.unqueued = 0;
7339			oldfire = hooks.empty.fire;
7340			hooks.empty.fire = function() {
7341				if ( !hooks.unqueued ) {
7342					oldfire();
7343				}
7344			};
7345		}
7346		hooks.unqueued++;
7347
7348		anim.always( function() {
7349
7350			// Ensure the complete handler is called before this completes
7351			anim.always( function() {
7352				hooks.unqueued--;
7353				if ( !jQuery.queue( elem, "fx" ).length ) {
7354					hooks.empty.fire();
7355				}
7356			} );
7357		} );
7358	}
7359
7360	// Detect show/hide animations
7361	for ( prop in props ) {
7362		value = props[ prop ];
7363		if ( rfxtypes.test( value ) ) {
7364			delete props[ prop ];
7365			toggle = toggle || value === "toggle";
7366			if ( value === ( hidden ? "hide" : "show" ) ) {
7367
7368				// Pretend to be hidden if this is a "show" and
7369				// there is still data from a stopped show/hide
7370				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
7371					hidden = true;
7372
7373				// Ignore all other no-op show/hide data
7374				} else {
7375					continue;
7376				}
7377			}
7378			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
7379		}
7380	}
7381
7382	// Bail out if this is a no-op like .hide().hide()
7383	propTween = !jQuery.isEmptyObject( props );
7384	if ( !propTween && jQuery.isEmptyObject( orig ) ) {
7385		return;
7386	}
7387
7388	// Restrict "overflow" and "display" styles during box animations
7389	if ( isBox && elem.nodeType === 1 ) {
7390
7391		// Support: IE <=9 - 11, Edge 12 - 15
7392		// Record all 3 overflow attributes because IE does not infer the shorthand
7393		// from identically-valued overflowX and overflowY and Edge just mirrors
7394		// the overflowX value there.
7395		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
7396
7397		// Identify a display type, preferring old show/hide data over the CSS cascade
7398		restoreDisplay = dataShow && dataShow.display;
7399		if ( restoreDisplay == null ) {
7400			restoreDisplay = dataPriv.get( elem, "display" );
7401		}
7402		display = jQuery.css( elem, "display" );
7403		if ( display === "none" ) {
7404			if ( restoreDisplay ) {
7405				display = restoreDisplay;
7406			} else {
7407
7408				// Get nonempty value(s) by temporarily forcing visibility
7409				showHide( [ elem ], true );
7410				restoreDisplay = elem.style.display || restoreDisplay;
7411				display = jQuery.css( elem, "display" );
7412				showHide( [ elem ] );
7413			}
7414		}
7415
7416		// Animate inline elements as inline-block
7417		if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
7418			if ( jQuery.css( elem, "float" ) === "none" ) {
7419
7420				// Restore the original display value at the end of pure show/hide animations
7421				if ( !propTween ) {
7422					anim.done( function() {
7423						style.display = restoreDisplay;
7424					} );
7425					if ( restoreDisplay == null ) {
7426						display = style.display;
7427						restoreDisplay = display === "none" ? "" : display;
7428					}
7429				}
7430				style.display = "inline-block";
7431			}
7432		}
7433	}
7434
7435	if ( opts.overflow ) {
7436		style.overflow = "hidden";
7437		anim.always( function() {
7438			style.overflow = opts.overflow[ 0 ];
7439			style.overflowX = opts.overflow[ 1 ];
7440			style.overflowY = opts.overflow[ 2 ];
7441		} );
7442	}
7443
7444	// Implement show/hide animations
7445	propTween = false;
7446	for ( prop in orig ) {
7447
7448		// General show/hide setup for this element animation
7449		if ( !propTween ) {
7450			if ( dataShow ) {
7451				if ( "hidden" in dataShow ) {
7452					hidden = dataShow.hidden;
7453				}
7454			} else {
7455				dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
7456			}
7457
7458			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
7459			if ( toggle ) {
7460				dataShow.hidden = !hidden;
7461			}
7462
7463			// Show elements before animating them
7464			if ( hidden ) {
7465				showHide( [ elem ], true );
7466			}
7467
7468			/* eslint-disable no-loop-func */
7469
7470			anim.done( function() {
7471
7472			/* eslint-enable no-loop-func */
7473
7474				// The final step of a "hide" animation is actually hiding the element
7475				if ( !hidden ) {
7476					showHide( [ elem ] );
7477				}
7478				dataPriv.remove( elem, "fxshow" );
7479				for ( prop in orig ) {
7480					jQuery.style( elem, prop, orig[ prop ] );
7481				}
7482			} );
7483		}
7484
7485		// Per-property setup
7486		propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
7487		if ( !( prop in dataShow ) ) {
7488			dataShow[ prop ] = propTween.start;
7489			if ( hidden ) {
7490				propTween.end = propTween.start;
7491				propTween.start = 0;
7492			}
7493		}
7494	}
7495}
7496
7497function propFilter( props, specialEasing ) {
7498	var index, name, easing, value, hooks;
7499
7500	// camelCase, specialEasing and expand cssHook pass
7501	for ( index in props ) {
7502		name = camelCase( index );
7503		easing = specialEasing[ name ];
7504		value = props[ index ];
7505		if ( Array.isArray( value ) ) {
7506			easing = value[ 1 ];
7507			value = props[ index ] = value[ 0 ];
7508		}
7509
7510		if ( index !== name ) {
7511			props[ name ] = value;
7512			delete props[ index ];
7513		}
7514
7515		hooks = jQuery.cssHooks[ name ];
7516		if ( hooks && "expand" in hooks ) {
7517			value = hooks.expand( value );
7518			delete props[ name ];
7519
7520			// Not quite $.extend, this won't overwrite existing keys.
7521			// Reusing 'index' because we have the correct "name"
7522			for ( index in value ) {
7523				if ( !( index in props ) ) {
7524					props[ index ] = value[ index ];
7525					specialEasing[ index ] = easing;
7526				}
7527			}
7528		} else {
7529			specialEasing[ name ] = easing;
7530		}
7531	}
7532}
7533
7534function Animation( elem, properties, options ) {
7535	var result,
7536		stopped,
7537		index = 0,
7538		length = Animation.prefilters.length,
7539		deferred = jQuery.Deferred().always( function() {
7540
7541			// Don't match elem in the :animated selector
7542			delete tick.elem;
7543		} ),
7544		tick = function() {
7545			if ( stopped ) {
7546				return false;
7547			}
7548			var currentTime = fxNow || createFxNow(),
7549				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
7550
7551				// Support: Android 2.3 only
7552				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
7553				temp = remaining / animation.duration || 0,
7554				percent = 1 - temp,
7555				index = 0,
7556				length = animation.tweens.length;
7557
7558			for ( ; index < length; index++ ) {
7559				animation.tweens[ index ].run( percent );
7560			}
7561
7562			deferred.notifyWith( elem, [ animation, percent, remaining ] );
7563
7564			// If there's more to do, yield
7565			if ( percent < 1 && length ) {
7566				return remaining;
7567			}
7568
7569			// If this was an empty animation, synthesize a final progress notification
7570			if ( !length ) {
7571				deferred.notifyWith( elem, [ animation, 1, 0 ] );
7572			}
7573
7574			// Resolve the animation and report its conclusion
7575			deferred.resolveWith( elem, [ animation ] );
7576			return false;
7577		},
7578		animation = deferred.promise( {
7579			elem: elem,
7580			props: jQuery.extend( {}, properties ),
7581			opts: jQuery.extend( true, {
7582				specialEasing: {},
7583				easing: jQuery.easing._default
7584			}, options ),
7585			originalProperties: properties,
7586			originalOptions: options,
7587			startTime: fxNow || createFxNow(),
7588			duration: options.duration,
7589			tweens: [],
7590			createTween: function( prop, end ) {
7591				var tween = jQuery.Tween( elem, animation.opts, prop, end,
7592						animation.opts.specialEasing[ prop ] || animation.opts.easing );
7593				animation.tweens.push( tween );
7594				return tween;
7595			},
7596			stop: function( gotoEnd ) {
7597				var index = 0,
7598
7599					// If we are going to the end, we want to run all the tweens
7600					// otherwise we skip this part
7601					length = gotoEnd ? animation.tweens.length : 0;
7602				if ( stopped ) {
7603					return this;
7604				}
7605				stopped = true;
7606				for ( ; index < length; index++ ) {
7607					animation.tweens[ index ].run( 1 );
7608				}
7609
7610				// Resolve when we played the last frame; otherwise, reject
7611				if ( gotoEnd ) {
7612					deferred.notifyWith( elem, [ animation, 1, 0 ] );
7613					deferred.resolveWith( elem, [ animation, gotoEnd ] );
7614				} else {
7615					deferred.rejectWith( elem, [ animation, gotoEnd ] );
7616				}
7617				return this;
7618			}
7619		} ),
7620		props = animation.props;
7621
7622	propFilter( props, animation.opts.specialEasing );
7623
7624	for ( ; index < length; index++ ) {
7625		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
7626		if ( result ) {
7627			if ( isFunction( result.stop ) ) {
7628				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
7629					result.stop.bind( result );
7630			}
7631			return result;
7632		}
7633	}
7634
7635	jQuery.map( props, createTween, animation );
7636
7637	if ( isFunction( animation.opts.start ) ) {
7638		animation.opts.start.call( elem, animation );
7639	}
7640
7641	// Attach callbacks from options
7642	animation
7643		.progress( animation.opts.progress )
7644		.done( animation.opts.done, animation.opts.complete )
7645		.fail( animation.opts.fail )
7646		.always( animation.opts.always );
7647
7648	jQuery.fx.timer(
7649		jQuery.extend( tick, {
7650			elem: elem,
7651			anim: animation,
7652			queue: animation.opts.queue
7653		} )
7654	);
7655
7656	return animation;
7657}
7658
7659jQuery.Animation = jQuery.extend( Animation, {
7660
7661	tweeners: {
7662		"*": [ function( prop, value ) {
7663			var tween = this.createTween( prop, value );
7664			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
7665			return tween;
7666		} ]
7667	},
7668
7669	tweener: function( props, callback ) {
7670		if ( isFunction( props ) ) {
7671			callback = props;
7672			props = [ "*" ];
7673		} else {
7674			props = props.match( rnothtmlwhite );
7675		}
7676
7677		var prop,
7678			index = 0,
7679			length = props.length;
7680
7681		for ( ; index < length; index++ ) {
7682			prop = props[ index ];
7683			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
7684			Animation.tweeners[ prop ].unshift( callback );
7685		}
7686	},
7687
7688	prefilters: [ defaultPrefilter ],
7689
7690	prefilter: function( callback, prepend ) {
7691		if ( prepend ) {
7692			Animation.prefilters.unshift( callback );
7693		} else {
7694			Animation.prefilters.push( callback );
7695		}
7696	}
7697} );
7698
7699jQuery.speed = function( speed, easing, fn ) {
7700	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
7701		complete: fn || !fn && easing ||
7702			isFunction( speed ) && speed,
7703		duration: speed,
7704		easing: fn && easing || easing && !isFunction( easing ) && easing
7705	};
7706
7707	// Go to the end state if fx are off
7708	if ( jQuery.fx.off ) {
7709		opt.duration = 0;
7710
7711	} else {
7712		if ( typeof opt.duration !== "number" ) {
7713			if ( opt.duration in jQuery.fx.speeds ) {
7714				opt.duration = jQuery.fx.speeds[ opt.duration ];
7715
7716			} else {
7717				opt.duration = jQuery.fx.speeds._default;
7718			}
7719		}
7720	}
7721
7722	// Normalize opt.queue - true/undefined/null -> "fx"
7723	if ( opt.queue == null || opt.queue === true ) {
7724		opt.queue = "fx";
7725	}
7726
7727	// Queueing
7728	opt.old = opt.complete;
7729
7730	opt.complete = function() {
7731		if ( isFunction( opt.old ) ) {
7732			opt.old.call( this );
7733		}
7734
7735		if ( opt.queue ) {
7736			jQuery.dequeue( this, opt.queue );
7737		}
7738	};
7739
7740	return opt;
7741};
7742
7743jQuery.fn.extend( {
7744	fadeTo: function( speed, to, easing, callback ) {
7745
7746		// Show any hidden elements after setting opacity to 0
7747		return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
7748
7749			// Animate to the value specified
7750			.end().animate( { opacity: to }, speed, easing, callback );
7751	},
7752	animate: function( prop, speed, easing, callback ) {
7753		var empty = jQuery.isEmptyObject( prop ),
7754			optall = jQuery.speed( speed, easing, callback ),
7755			doAnimation = function() {
7756
7757				// Operate on a copy of prop so per-property easing won't be lost
7758				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
7759
7760				// Empty animations, or finishing resolves immediately
7761				if ( empty || dataPriv.get( this, "finish" ) ) {
7762					anim.stop( true );
7763				}
7764			};
7765			doAnimation.finish = doAnimation;
7766
7767		return empty || optall.queue === false ?
7768			this.each( doAnimation ) :
7769			this.queue( optall.queue, doAnimation );
7770	},
7771	stop: function( type, clearQueue, gotoEnd ) {
7772		var stopQueue = function( hooks ) {
7773			var stop = hooks.stop;
7774			delete hooks.stop;
7775			stop( gotoEnd );
7776		};
7777
7778		if ( typeof type !== "string" ) {
7779			gotoEnd = clearQueue;
7780			clearQueue = type;
7781			type = undefined;
7782		}
7783		if ( clearQueue 
vendor: 3,719 bytes, lines 7783-7941
7783) {
7784			this.queue( type || "fx", [] );
7785		}
7786
7787		return this.each( function() {
7788			var dequeue = true,
7789				index = type != null && type + "queueHooks",
7790				timers = jQuery.timers,
7791				data = dataPriv.get( this );
7792
7793			if ( index ) {
7794				if ( data[ index ] && data[ index ].stop ) {
7795					stopQueue( data[ index ] );
7796				}
7797			} else {
7798				for ( index in data ) {
7799					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
7800						stopQueue( data[ index ] );
7801					}
7802				}
7803			}
7804
7805			for ( index = timers.length; index--; ) {
7806				if ( timers[ index ].elem === this &&
7807					( type == null || timers[ index ].queue === type ) ) {
7808
7809					timers[ index ].anim.stop( gotoEnd );
7810					dequeue = false;
7811					timers.splice( index, 1 );
7812				}
7813			}
7814
7815			// Start the next in the queue if the last step wasn't forced.
7816			// Timers currently will call their complete callbacks, which
7817			// will dequeue but only if they were gotoEnd.
7818			if ( dequeue || !gotoEnd ) {
7819				jQuery.dequeue( this, type );
7820			}
7821		} );
7822	},
7823	finish: function( type ) {
7824		if ( type !== false ) {
7825			type = type || "fx";
7826		}
7827		return this.each( function() {
7828			var index,
7829				data = dataPriv.get( this ),
7830				queue = data[ type + "queue" ],
7831				hooks = data[ type + "queueHooks" ],
7832				timers = jQuery.timers,
7833				length = queue ? queue.length : 0;
7834
7835			// Enable finishing flag on private data
7836			data.finish = true;
7837
7838			// Empty the queue first
7839			jQuery.queue( this, type, [] );
7840
7841			if ( hooks && hooks.stop ) {
7842				hooks.stop.call( this, true );
7843			}
7844
7845			// Look for any active animations, and finish them
7846			for ( index = timers.length; index--; ) {
7847				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
7848					timers[ index ].anim.stop( true );
7849					timers.splice( index, 1 );
7850				}
7851			}
7852
7853			// Look for any animations in the old queue and finish them
7854			for ( index = 0; index < length; index++ ) {
7855				if ( queue[ index ] && queue[ index ].finish ) {
7856					queue[ index ].finish.call( this );
7857				}
7858			}
7859
7860			// Turn off finishing flag
7861			delete data.finish;
7862		} );
7863	}
7864} );
7865
7866jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) {
7867	var cssFn = jQuery.fn[ name ];
7868	jQuery.fn[ name ] = function( speed, easing, callback ) {
7869		return speed == null || typeof speed === "boolean" ?
7870			cssFn.apply( this, arguments ) :
7871			this.animate( genFx( name, true ), speed, easing, callback );
7872	};
7873} );
7874
7875// Generate shortcuts for custom animations
7876jQuery.each( {
7877	slideDown: genFx( "show" ),
7878	slideUp: genFx( "hide" ),
7879	slideToggle: genFx( "toggle" ),
7880	fadeIn: { opacity: "show" },
7881	fadeOut: { opacity: "hide" },
7882	fadeToggle: { opacity: "toggle" }
7883}, function( name, props ) {
7884	jQuery.fn[ name ] = function( speed, easing, callback ) {
7885		return this.animate( props, speed, easing, callback );
7886	};
7887} );
7888
7889jQuery.timers = [];
7890jQuery.fx.tick = function() {
7891	var timer,
7892		i = 0,
7893		timers = jQuery.timers;
7894
7895	fxNow = Date.now();
7896
7897	for ( ; i < timers.length; i++ ) {
7898		timer = timers[ i ];
7899
7900		// Run the timer and safely remove it when done (allowing for external removal)
7901		if ( !timer() && timers[ i ] === timer ) {
7902			timers.splice( i--, 1 );
7903		}
7904	}
7905
7906	if ( !timers.length ) {
7907		jQuery.fx.stop();
7908	}
7909	fxNow = undefined;
7910};
7911
7912jQuery.fx.timer = function( timer ) {
7913	jQuery.timers.push( timer );
7914	jQuery.fx.start();
7915};
7916
7917jQuery.fx.interval = 13;
7918jQuery.fx.start = function() {
7919	if ( inProgress ) {
7920		return;
7921	}
7922
7923	inProgress = true;
7924	schedule();
7925};
7926
7927jQuery.fx.stop = function() {
7928	inProgress = null;
7929};
7930
7931jQuery.fx.speeds = {
7932	slow: 600,
7933	fast: 200,
7934
7935	// Default speed
7936	_default: 400
7937};
7938
7939
7940// Based off of the plugin by Clint Helfers, with permission.
7941// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
vendor: 5,177 bytes, lines 7942-8162
7942jQuery.fn.delay = function( time, type ) {
7943	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
7944	type = type || "fx";
7945
7946	return this.queue( type, function( next, hooks ) {
7947		var timeout = window.setTimeout( next, time );
7948		hooks.stop = function() {
7949			window.clearTimeout( timeout );
7950		};
7951	} );
7952};
7953
7954
7955( function() {
7956	var input = document.createElement( "input" ),
7957		select = document.createElement( "select" ),
7958		opt = select.appendChild( document.createElement( "option" ) );
7959
7960	input.type = "checkbox";
7961
7962	// Support: Android <=4.3 only
7963	// Default value for a checkbox should be "on"
7964	support.checkOn = input.value !== "";
7965
7966	// Support: IE <=11 only
7967	// Must access selectedIndex to make default options select
7968	support.optSelected = opt.selected;
7969
7970	// Support: IE <=11 only
7971	// An input loses its value after becoming a radio
7972	input = document.createElement( "input" );
7973	input.value = "t";
7974	input.type = "radio";
7975	support.radioValue = input.value === "t";
7976} )();
7977
7978
7979var boolHook,
7980	attrHandle = jQuery.expr.attrHandle;
7981
7982jQuery.fn.extend( {
7983	attr: function( name, value ) {
7984		return access( this, jQuery.attr, name, value, arguments.length > 1 );
7985	},
7986
7987	removeAttr: function( name ) {
7988		return this.each( function() {
7989			jQuery.removeAttr( this, name );
7990		} );
7991	}
7992} );
7993
7994jQuery.extend( {
7995	attr: function( elem, name, value ) {
7996		var ret, hooks,
7997			nType = elem.nodeType;
7998
7999		// Don't get/set attributes on text, comment and attribute nodes
8000		if ( nType === 3 || nType === 8 || nType === 2 ) {
8001			return;
8002		}
8003
8004		// Fallback to prop when attributes are not supported
8005		if ( typeof elem.getAttribute === "undefined" ) {
8006			return jQuery.prop( elem, name, value );
8007		}
8008
8009		// Attribute hooks are determined by the lowercase version
8010		// Grab necessary hook if one is defined
8011		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
8012			hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
8013				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
8014		}
8015
8016		if ( value !== undefined ) {
8017			if ( value === null ) {
8018				jQuery.removeAttr( elem, name );
8019				return;
8020			}
8021
8022			if ( hooks && "set" in hooks &&
8023				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
8024				return ret;
8025			}
8026
8027			elem.setAttribute( name, value + "" );
8028			return value;
8029		}
8030
8031		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
8032			return ret;
8033		}
8034
8035		ret = jQuery.find.attr( elem, name );
8036
8037		// Non-existent attributes return null, we normalize to undefined
8038		return ret == null ? undefined : ret;
8039	},
8040
8041	attrHooks: {
8042		type: {
8043			set: function( elem, value ) {
8044				if ( !support.radioValue && value === "radio" &&
8045					nodeName( elem, "input" ) ) {
8046					var val = elem.value;
8047					elem.setAttribute( "type", value );
8048					if ( val ) {
8049						elem.value = val;
8050					}
8051					return value;
8052				}
8053			}
8054		}
8055	},
8056
8057	removeAttr: function( elem, value ) {
8058		var name,
8059			i = 0,
8060
8061			// Attribute names can contain non-HTML whitespace characters
8062			// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
8063			attrNames = value && value.match( rnothtmlwhite );
8064
8065		if ( attrNames && elem.nodeType === 1 ) {
8066			while ( ( name = attrNames[ i++ ] ) ) {
8067				elem.removeAttribute( name );
8068			}
8069		}
8070	}
8071} );
8072
8073// Hooks for boolean attributes
8074boolHook = {
8075	set: function( elem, value, name ) {
8076		if ( value === false ) {
8077
8078			// Remove boolean attributes when set to false
8079			jQuery.removeAttr( elem, name );
8080		} else {
8081			elem.setAttribute( name, name );
8082		}
8083		return name;
8084	}
8085};
8086
8087jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
8088	var getter = attrHandle[ name ] || jQuery.find.attr;
8089
8090	attrHandle[ name ] = function( elem, name, isXML ) {
8091		var ret, handle,
8092			lowercaseName = name.toLowerCase();
8093
8094		if ( !isXML ) {
8095
8096			// Avoid an infinite loop by temporarily removing this function from the getter
8097			handle = attrHandle[ lowercaseName ];
8098			attrHandle[ lowercaseName ] = ret;
8099			ret = getter( elem, name, isXML ) != null ?
8100				lowercaseName :
8101				null;
8102			attrHandle[ lowercaseName ] = handle;
8103		}
8104		return ret;
8105	};
8106} );
8107
8108
8109
8110
8111var rfocusable = /^(?:input|select|textarea|button)$/i,
8112	rclickable = /^(?:a|area)$/i;
8113
8114jQuery.fn.extend( {
8115	prop: function( name, value ) {
8116		return access( this, jQuery.prop, name, value, arguments.length > 1 );
8117	},
8118
8119	removeProp: function( name ) {
8120		return this.each( function() {
8121			delete this[ jQuery.propFix[ name ] || name ];
8122		} );
8123	}
8124} );
8125
8126jQuery.extend( {
8127	prop: function( elem, name, value ) {
8128		var ret, hooks,
8129			nType = elem.nodeType;
8130
8131		// Don't get/set properties on text, comment and attribute nodes
8132		if ( nType === 3 || nType === 8 || nType === 2 ) {
8133			return;
8134		}
8135
8136		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
8137
8138			// Fix name and attach hooks
8139			name = jQuery.propFix[ name ] || name;
8140			hooks = jQuery.propHooks[ name ];
8141		}
8142
8143		if ( value !== undefined ) {
8144			if ( hooks && "set" in hooks &&
8145				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
8146				return ret;
8147			}
8148
8149			return ( elem[ name ] = value );
8150		}
8151
8152		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
8153			return ret;
8154		}
8155
8156		return elem[ name ];
8157	},
8158
8159	propHooks: {
8160		tabIndex: {
8161			get: function( elem ) {
8162
vendor: 6,193 bytes, lines 8163-8425
8163				// Support: IE <=9 - 11 only
8164				// elem.tabIndex doesn't always return the
8165				// correct value when it hasn't been explicitly set
8166				// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
8167				// Use proper attribute retrieval(#12072)
8168				var tabindex = jQuery.find.attr( elem, "tabindex" );
8169
8170				if ( tabindex ) {
8171					return parseInt( tabindex, 10 );
8172				}
8173
8174				if (
8175					rfocusable.test( elem.nodeName ) ||
8176					rclickable.test( elem.nodeName ) &&
8177					elem.href
8178				) {
8179					return 0;
8180				}
8181
8182				return -1;
8183			}
8184		}
8185	},
8186
8187	propFix: {
8188		"for": "htmlFor",
8189		"class": "className"
8190	}
8191} );
8192
8193// Support: IE <=11 only
8194// Accessing the selectedIndex property
8195// forces the browser to respect setting selected
8196// on the option
8197// The getter ensures a default option is selected
8198// when in an optgroup
8199// eslint rule "no-unused-expressions" is disabled for this code
8200// since it considers such accessions noop
8201if ( !support.optSelected ) {
8202	jQuery.propHooks.selected = {
8203		get: function( elem ) {
8204
8205			/* eslint no-unused-expressions: "off" */
8206
8207			var parent = elem.parentNode;
8208			if ( parent && parent.parentNode ) {
8209				parent.parentNode.selectedIndex;
8210			}
8211			return null;
8212		},
8213		set: function( elem ) {
8214
8215			/* eslint no-unused-expressions: "off" */
8216
8217			var parent = elem.parentNode;
8218			if ( parent ) {
8219				parent.selectedIndex;
8220
8221				if ( parent.parentNode ) {
8222					parent.parentNode.selectedIndex;
8223				}
8224			}
8225		}
8226	};
8227}
8228
8229jQuery.each( [
8230	"tabIndex",
8231	"readOnly",
8232	"maxLength",
8233	"cellSpacing",
8234	"cellPadding",
8235	"rowSpan",
8236	"colSpan",
8237	"useMap",
8238	"frameBorder",
8239	"contentEditable"
8240], function() {
8241	jQuery.propFix[ this.toLowerCase() ] = this;
8242} );
8243
8244
8245
8246
8247	// Strip and collapse whitespace according to HTML spec
8248	// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
8249	function stripAndCollapse( value ) {
8250		var tokens = value.match( rnothtmlwhite ) || [];
8251		return tokens.join( " " );
8252	}
8253
8254
8255function getClass( elem ) {
8256	return elem.getAttribute && elem.getAttribute( "class" ) || "";
8257}
8258
8259function classesToArray( value ) {
8260	if ( Array.isArray( value ) ) {
8261		return value;
8262	}
8263	if ( typeof value === "string" ) {
8264		return value.match( rnothtmlwhite ) || [];
8265	}
8266	return [];
8267}
8268
8269jQuery.fn.extend( {
8270	addClass: function( value ) {
8271		var classes, elem, cur, curValue, clazz, j, finalValue,
8272			i = 0;
8273
8274		if ( isFunction( value ) ) {
8275			return this.each( function( j ) {
8276				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
8277			} );
8278		}
8279
8280		classes = classesToArray( value );
8281
8282		if ( classes.length ) {
8283			while ( ( elem = this[ i++ ] ) ) {
8284				curValue = getClass( elem );
8285				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
8286
8287				if ( cur ) {
8288					j = 0;
8289					while ( ( clazz = classes[ j++ ] ) ) {
8290						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
8291							cur += clazz + " ";
8292						}
8293					}
8294
8295					// Only assign if different to avoid unneeded rendering.
8296					finalValue = stripAndCollapse( cur );
8297					if ( curValue !== finalValue ) {
8298						elem.setAttribute( "class", finalValue );
8299					}
8300				}
8301			}
8302		}
8303
8304		return this;
8305	},
8306
8307	removeClass: function( value ) {
8308		var classes, elem, cur, curValue, clazz, j, finalValue,
8309			i = 0;
8310
8311		if ( isFunction( value ) ) {
8312			return this.each( function( j ) {
8313				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
8314			} );
8315		}
8316
8317		if ( !arguments.length ) {
8318			return this.attr( "class", "" );
8319		}
8320
8321		classes = classesToArray( value );
8322
8323		if ( classes.length ) {
8324			while ( ( elem = this[ i++ ] ) ) {
8325				curValue = getClass( elem );
8326
8327				// This expression is here for better compressibility (see addClass)
8328				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
8329
8330				if ( cur ) {
8331					j = 0;
8332					while ( ( clazz = classes[ j++ ] ) ) {
8333
8334						// Remove *all* instances
8335						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
8336							cur = cur.replace( " " + clazz + " ", " " );
8337						}
8338					}
8339
8340					// Only assign if different to avoid unneeded rendering.
8341					finalValue = stripAndCollapse( cur );
8342					if ( curValue !== finalValue ) {
8343						elem.setAttribute( "class", finalValue );
8344					}
8345				}
8346			}
8347		}
8348
8349		return this;
8350	},
8351
8352	toggleClass: function( value, stateVal ) {
8353		var type = typeof value,
8354			isValidValue = type === "string" || Array.isArray( value );
8355
8356		if ( typeof stateVal === "boolean" && isValidValue ) {
8357			return stateVal ? this.addClass( value ) : this.removeClass( value );
8358		}
8359
8360		if ( isFunction( value ) ) {
8361			return this.each( function( i ) {
8362				jQuery( this ).toggleClass(
8363					value.call( this, i, getClass( this ), stateVal ),
8364					stateVal
8365				);
8366			} );
8367		}
8368
8369		return this.each( function() {
8370			var className, i, self, classNames;
8371
8372			if ( isValidValue ) {
8373
8374				// Toggle individual class names
8375				i = 0;
8376				self = jQuery( this );
8377				classNames = classesToArray( value );
8378
8379				while ( ( className = classNames[ i++ ] ) ) {
8380
8381					// Check each className given, space separated list
8382					if ( self.hasClass( className ) ) {
8383						self.removeClass( className );
8384					} else {
8385						self.addClass( className );
8386					}
8387				}
8388
8389			// Toggle whole class name
8390			} else if ( value === undefined || type === "boolean" ) {
8391				className = getClass( this );
8392				if ( className ) {
8393
8394					// Store className if set
8395					dataPriv.set( this, "__className__", className );
8396				}
8397
8398				// If the element has a class name or if we're passed `false`,
8399				// then remove the whole classname (if there was one, the above saved it).
8400				// Otherwise bring back whatever was previously saved (if anything),
8401				// falling back to the empty string if nothing was stored.
8402				if ( this.setAttribute ) {
8403					this.setAttribute( "class",
8404						className || value === false ?
8405						"" :
8406						dataPriv.get( this, "__className__" ) || ""
8407					);
8408				}
8409			}
8410		} );
8411	},
8412
8413	hasClass: function( selector ) {
8414		var className, elem,
8415			i = 0;
8416
8417		className = " " + selector + " ";
8418		while ( ( elem = this[ i++ ] ) ) {
8419			if ( elem.nodeType === 1 &&
8420				( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
8421					return true;
8422			}
8423		}
8424
8425		return false;
vendor: 7,091 bytes, lines 8426-8711
8426	}
8427} );
8428
8429
8430
8431
8432var rreturn = /\r/g;
8433
8434jQuery.fn.extend( {
8435	val: function( value ) {
8436		var hooks, ret, valueIsFunction,
8437			elem = this[ 0 ];
8438
8439		if ( !arguments.length ) {
8440			if ( elem ) {
8441				hooks = jQuery.valHooks[ elem.type ] ||
8442					jQuery.valHooks[ elem.nodeName.toLowerCase() ];
8443
8444				if ( hooks &&
8445					"get" in hooks &&
8446					( ret = hooks.get( elem, "value" ) ) !== undefined
8447				) {
8448					return ret;
8449				}
8450
8451				ret = elem.value;
8452
8453				// Handle most common string cases
8454				if ( typeof ret === "string" ) {
8455					return ret.replace( rreturn, "" );
8456				}
8457
8458				// Handle cases where value is null/undef or number
8459				return ret == null ? "" : ret;
8460			}
8461
8462			return;
8463		}
8464
8465		valueIsFunction = isFunction( value );
8466
8467		return this.each( function( i ) {
8468			var val;
8469
8470			if ( this.nodeType !== 1 ) {
8471				return;
8472			}
8473
8474			if ( valueIsFunction ) {
8475				val = value.call( this, i, jQuery( this ).val() );
8476			} else {
8477				val = value;
8478			}
8479
8480			// Treat null/undefined as ""; convert numbers to string
8481			if ( val == null ) {
8482				val = "";
8483
8484			} else if ( typeof val === "number" ) {
8485				val += "";
8486
8487			} else if ( Array.isArray( val ) ) {
8488				val = jQuery.map( val, function( value ) {
8489					return value == null ? "" : value + "";
8490				} );
8491			}
8492
8493			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
8494
8495			// If set returns undefined, fall back to normal setting
8496			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
8497				this.value = val;
8498			}
8499		} );
8500	}
8501} );
8502
8503jQuery.extend( {
8504	valHooks: {
8505		option: {
8506			get: function( elem ) {
8507
8508				var val = jQuery.find.attr( elem, "value" );
8509				return val != null ?
8510					val :
8511
8512					// Support: IE <=10 - 11 only
8513					// option.text throws exceptions (#14686, #14858)
8514					// Strip and collapse whitespace
8515					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
8516					stripAndCollapse( jQuery.text( elem ) );
8517			}
8518		},
8519		select: {
8520			get: function( elem ) {
8521				var value, option, i,
8522					options = elem.options,
8523					index = elem.selectedIndex,
8524					one = elem.type === "select-one",
8525					values = one ? null : [],
8526					max = one ? index + 1 : options.length;
8527
8528				if ( index < 0 ) {
8529					i = max;
8530
8531				} else {
8532					i = one ? index : 0;
8533				}
8534
8535				// Loop through all the selected options
8536				for ( ; i < max; i++ ) {
8537					option = options[ i ];
8538
8539					// Support: IE <=9 only
8540					// IE8-9 doesn't update selected after form reset (#2551)
8541					if ( ( option.selected || i === index ) &&
8542
8543							// Don't return options that are disabled or in a disabled optgroup
8544							!option.disabled &&
8545							( !option.parentNode.disabled ||
8546								!nodeName( option.parentNode, "optgroup" ) ) ) {
8547
8548						// Get the specific value for the option
8549						value = jQuery( option ).val();
8550
8551						// We don't need an array for one selects
8552						if ( one ) {
8553							return value;
8554						}
8555
8556						// Multi-Selects return an array
8557						values.push( value );
8558					}
8559				}
8560
8561				return values;
8562			},
8563
8564			set: function( elem, value ) {
8565				var optionSet, option,
8566					options = elem.options,
8567					values = jQuery.makeArray( value ),
8568					i = options.length;
8569
8570				while ( i-- ) {
8571					option = options[ i ];
8572
8573					/* eslint-disable no-cond-assign */
8574
8575					if ( option.selected =
8576						jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
8577					) {
8578						optionSet = true;
8579					}
8580
8581					/* eslint-enable no-cond-assign */
8582				}
8583
8584				// Force browsers to behave consistently when non-matching value is set
8585				if ( !optionSet ) {
8586					elem.selectedIndex = -1;
8587				}
8588				return values;
8589			}
8590		}
8591	}
8592} );
8593
8594// Radios and checkboxes getter/setter
8595jQuery.each( [ "radio", "checkbox" ], function() {
8596	jQuery.valHooks[ this ] = {
8597		set: function( elem, value ) {
8598			if ( Array.isArray( value ) ) {
8599				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
8600			}
8601		}
8602	};
8603	if ( !support.checkOn ) {
8604		jQuery.valHooks[ this ].get = function( elem ) {
8605			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
8606		};
8607	}
8608} );
8609
8610
8611
8612
8613// Return jQuery for attributes-only inclusion
8614
8615
8616support.focusin = "onfocusin" in window;
8617
8618
8619var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
8620	stopPropagationCallback = function( e ) {
8621		e.stopPropagation();
8622	};
8623
8624jQuery.extend( jQuery.event, {
8625
8626	trigger: function( event, data, elem, onlyHandlers ) {
8627
8628		var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
8629			eventPath = [ elem || document ],
8630			type = hasOwn.call( event, "type" ) ? event.type : event,
8631			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
8632
8633		cur = lastElement = tmp = elem = elem || document;
8634
8635		// Don't do events on text and comment nodes
8636		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
8637			return;
8638		}
8639
8640		// focus/blur morphs to focusin/out; ensure we're not firing them right now
8641		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
8642			return;
8643		}
8644
8645		if ( type.indexOf( "." ) > -1 ) {
8646
8647			// Namespaced trigger; create a regexp to match event type in handle()
8648			namespaces = type.split( "." );
8649			type = namespaces.shift();
8650			namespaces.sort();
8651		}
8652		ontype = type.indexOf( ":" ) < 0 && "on" + type;
8653
8654		// Caller can pass in a jQuery.Event object, Object, or just an event type string
8655		event = event[ jQuery.expando ] ?
8656			event :
8657			new jQuery.Event( type, typeof event === "object" && event );
8658
8659		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
8660		event.isTrigger = onlyHandlers ? 2 : 3;
8661		event.namespace = namespaces.join( "." );
8662		event.rnamespace = event.namespace ?
8663			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
8664			null;
8665
8666		// Clean up the event in case it is being reused
8667		event.result = undefined;
8668		if ( !event.target ) {
8669			event.target = elem;
8670		}
8671
8672		// Clone any incoming data and prepend the event, creating the handler arg list
8673		data = data == null ?
8674			[ event ] :
8675			jQuery.makeArray( data, [ event ] );
8676
8677		// Allow special events to draw outside the lines
8678		special = jQuery.event.special[ type ] || {};
8679		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
8680			return;
8681		}
8682
8683		// Determine event propagation path in advance, per W3C events spec (#9951)
8684		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
8685		if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
8686
8687			bubbleType = special.delegateType || type;
8688			if ( !rfocusMorph.test( bubbleType + type ) ) {
8689				cur = cur.parentNode;
8690			}
8691			for ( ; cur; cur = cur.parentNode ) {
8692				eventPath.push( cur );
8693				tmp = cur;
8694			}
8695
8696			// Only add window if we got to document (e.g., not plain obj or detached DOM)
8697			if ( tmp === ( elem.ownerDocument || document ) ) {
8698				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
8699			}
8700		}
8701
8702		// Fire handlers on the event path
8703		i = 0;
8704		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
8705			lastElement = cur;
8706			event.type = i > 1 ?
8707				bubbleType :
8708				special.bindType || type;
8709
8710			// jQuery handler
8711			handle = (
vendor: 2,548 bytes, lines 8711-8812
8711
8712					dataPriv.get( cur, "events" ) || Object.create( null )
8713				)[ event.type ] &&
8714				dataPriv.get( cur, "handle" );
8715			if ( handle ) {
8716				handle.apply( cur, data );
8717			}
8718
8719			// Native handler
8720			handle = ontype && cur[ ontype ];
8721			if ( handle && handle.apply && acceptData( cur ) ) {
8722				event.result = handle.apply( cur, data );
8723				if ( event.result === false ) {
8724					event.preventDefault();
8725				}
8726			}
8727		}
8728		event.type = type;
8729
8730		// If nobody prevented the default action, do it now
8731		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
8732
8733			if ( ( !special._default ||
8734				special._default.apply( eventPath.pop(), data ) === false ) &&
8735				acceptData( elem ) ) {
8736
8737				// Call a native DOM method on the target with the same name as the event.
8738				// Don't do default actions on window, that's where global variables be (#6170)
8739				if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
8740
8741					// Don't re-trigger an onFOO event when we call its FOO() method
8742					tmp = elem[ ontype ];
8743
8744					if ( tmp ) {
8745						elem[ ontype ] = null;
8746					}
8747
8748					// Prevent re-triggering of the same event, since we already bubbled it above
8749					jQuery.event.triggered = type;
8750
8751					if ( event.isPropagationStopped() ) {
8752						lastElement.addEventListener( type, stopPropagationCallback );
8753					}
8754
8755					elem[ type ]();
8756
8757					if ( event.isPropagationStopped() ) {
8758						lastElement.removeEventListener( type, stopPropagationCallback );
8759					}
8760
8761					jQuery.event.triggered = undefined;
8762
8763					if ( tmp ) {
8764						elem[ ontype ] = tmp;
8765					}
8766				}
8767			}
8768		}
8769
8770		return event.result;
8771	},
8772
8773	// Piggyback on a donor event to simulate a different one
8774	// Used only for `focus(in | out)` events
8775	simulate: function( type, elem, event ) {
8776		var e = jQuery.extend(
8777			new jQuery.Event(),
8778			event,
8779			{
8780				type: type,
8781				isSimulated: true
8782			}
8783		);
8784
8785		jQuery.event.trigger( e, null, elem );
8786	}
8787
8788} );
8789
8790jQuery.fn.extend( {
8791
8792	trigger: function( type, data ) {
8793		return this.each( function() {
8794			jQuery.event.trigger( type, data, this );
8795		} );
8796	},
8797	triggerHandler: function( type, data ) {
8798		var elem = this[ 0 ];
8799		if ( elem ) {
8800			return jQuery.event.trigger( type, data, elem, true );
8801		}
8802	}
8803} );
8804
8805
8806// Support: Firefox <=44
8807// Firefox doesn't have focus(in | out) events
8808// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
8809//
8810// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
8811// focus(in | out) events fire after focus & blur events,
8812// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
vendor: 4,078 bytes, lines 8813-8978
8813// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
8814if ( !support.focusin ) {
8815	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
8816
8817		// Attach a single capturing handler on the document while someone wants focusin/focusout
8818		var handler = function( event ) {
8819			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
8820		};
8821
8822		jQuery.event.special[ fix ] = {
8823			setup: function() {
8824
8825				// Handle: regular nodes (via `this.ownerDocument`), window
8826				// (via `this.document`) & document (via `this`).
8827				var doc = this.ownerDocument || this.document || this,
8828					attaches = dataPriv.access( doc, fix );
8829
8830				if ( !attaches ) {
8831					doc.addEventListener( orig, handler, true );
8832				}
8833				dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
8834			},
8835			teardown: function() {
8836				var doc = this.ownerDocument || this.document || this,
8837					attaches = dataPriv.access( doc, fix ) - 1;
8838
8839				if ( !attaches ) {
8840					doc.removeEventListener( orig, handler, true );
8841					dataPriv.remove( doc, fix );
8842
8843				} else {
8844					dataPriv.access( doc, fix, attaches );
8845				}
8846			}
8847		};
8848	} );
8849}
8850var location = window.location;
8851
8852var nonce = { guid: Date.now() };
8853
8854var rquery = ( /\?/ );
8855
8856
8857
8858// Cross-browser xml parsing
8859jQuery.parseXML = function( data ) {
8860	var xml;
8861	if ( !data || typeof data !== "string" ) {
8862		return null;
8863	}
8864
8865	// Support: IE 9 - 11 only
8866	// IE throws on parseFromString with invalid input.
8867	try {
8868		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
8869	} catch ( e ) {
8870		xml = undefined;
8871	}
8872
8873	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
8874		jQuery.error( "Invalid XML: " + data );
8875	}
8876	return xml;
8877};
8878
8879
8880var
8881	rbracket = /\[\]$/,
8882	rCRLF = /\r?\n/g,
8883	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
8884	rsubmittable = /^(?:input|select|textarea|keygen)/i;
8885
8886function buildParams( prefix, obj, traditional, add ) {
8887	var name;
8888
8889	if ( Array.isArray( obj ) ) {
8890
8891		// Serialize array item.
8892		jQuery.each( obj, function( i, v ) {
8893			if ( traditional || rbracket.test( prefix ) ) {
8894
8895				// Treat each array item as a scalar.
8896				add( prefix, v );
8897
8898			} else {
8899
8900				// Item is non-scalar (array or object), encode its numeric index.
8901				buildParams(
8902					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
8903					v,
8904					traditional,
8905					add
8906				);
8907			}
8908		} );
8909
8910	} else if ( !traditional && toType( obj ) === "object" ) {
8911
8912		// Serialize object item.
8913		for ( name in obj ) {
8914			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
8915		}
8916
8917	} else {
8918
8919		// Serialize scalar item.
8920		add( prefix, obj );
8921	}
8922}
8923
8924// Serialize an array of form elements or a set of
8925// key/values into a query string
8926jQuery.param = function( a, traditional ) {
8927	var prefix,
8928		s = [],
8929		add = function( key, valueOrFunction ) {
8930
8931			// If value is a function, invoke it and use its return value
8932			var value = isFunction( valueOrFunction ) ?
8933				valueOrFunction() :
8934				valueOrFunction;
8935
8936			s[ s.length ] = encodeURIComponent( key ) + "=" +
8937				encodeURIComponent( value == null ? "" : value );
8938		};
8939
8940	if ( a == null ) {
8941		return "";
8942	}
8943
8944	// If an array was passed in, assume that it is an array of form elements.
8945	if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
8946
8947		// Serialize the form elements
8948		jQuery.each( a, function() {
8949			add( this.name, this.value );
8950		} );
8951
8952	} else {
8953
8954		// If traditional, encode the "old" way (the way 1.3.2 or older
8955		// did it), otherwise encode params recursively.
8956		for ( prefix in a ) {
8957			buildParams( prefix, a[ prefix ], traditional, add );
8958		}
8959	}
8960
8961	// Return the resulting serialization
8962	return s.join( "&" );
8963};
8964
8965jQuery.fn.extend( {
8966	serialize: function() {
8967		return jQuery.param( this.serializeArray() );
8968	},
8969	serializeArray: function() {
8970		return this.map( function() {
8971
8972			// Can add propHook for "elements" to filter or add form elements
8973			var elements = jQuery.prop( this, "elements" );
8974			return elements ? jQuery.makeArray( elements ) : this;
8975		} )
8976		.filter( function() {
8977			var type = this.type;
8978
vendor: 4,933 bytes, lines 8979-9151
8979			// Use .is( ":disabled" ) so that fieldset[disabled] works
8980			return this.name && !jQuery( this ).is( ":disabled" ) &&
8981				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
8982				( this.checked || !rcheckableType.test( type ) );
8983		} )
8984		.map( function( _i, elem ) {
8985			var val = jQuery( this ).val();
8986
8987			if ( val == null ) {
8988				return null;
8989			}
8990
8991			if ( Array.isArray( val ) ) {
8992				return jQuery.map( val, function( val ) {
8993					return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
8994				} );
8995			}
8996
8997			return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
8998		} ).get();
8999	}
9000} );
9001
9002
9003var
9004	r20 = /%20/g,
9005	rhash = /#.*$/,
9006	rantiCache = /([?&])_=[^&]*/,
9007	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
9008
9009	// #7653, #8125, #8152: local protocol detection
9010	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
9011	rnoContent = /^(?:GET|HEAD)$/,
9012	rprotocol = /^\/\//,
9013
9014	/* Prefilters
9015	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
9016	 * 2) These are called:
9017	 *    - BEFORE asking for a transport
9018	 *    - AFTER param serialization (s.data is a string if s.processData is true)
9019	 * 3) key is the dataType
9020	 * 4) the catchall symbol "*" can be used
9021	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
9022	 */
9023	prefilters = {},
9024
9025	/* Transports bindings
9026	 * 1) key is the dataType
9027	 * 2) the catchall symbol "*" can be used
9028	 * 3) selection will start with transport dataType and THEN go to "*" if needed
9029	 */
9030	transports = {},
9031
9032	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
9033	allTypes = "*/".concat( "*" ),
9034
9035	// Anchor tag for parsing the document origin
9036	originAnchor = document.createElement( "a" );
9037	originAnchor.href = location.href;
9038
9039// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
9040function addToPrefiltersOrTransports( structure ) {
9041
9042	// dataTypeExpression is optional and defaults to "*"
9043	return function( dataTypeExpression, func ) {
9044
9045		if ( typeof dataTypeExpression !== "string" ) {
9046			func = dataTypeExpression;
9047			dataTypeExpression = "*";
9048		}
9049
9050		var dataType,
9051			i = 0,
9052			dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
9053
9054		if ( isFunction( func ) ) {
9055
9056			// For each dataType in the dataTypeExpression
9057			while ( ( dataType = dataTypes[ i++ ] ) ) {
9058
9059				// Prepend if requested
9060				if ( dataType[ 0 ] === "+" ) {
9061					dataType = dataType.slice( 1 ) || "*";
9062					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
9063
9064				// Otherwise append
9065				} else {
9066					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
9067				}
9068			}
9069		}
9070	};
9071}
9072
9073// Base inspection function for prefilters and transports
9074function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
9075
9076	var inspected = {},
9077		seekingTransport = ( structure === transports );
9078
9079	function inspect( dataType ) {
9080		var selected;
9081		inspected[ dataType ] = true;
9082		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
9083			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
9084			if ( typeof dataTypeOrTransport === "string" &&
9085				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
9086
9087				options.dataTypes.unshift( dataTypeOrTransport );
9088				inspect( dataTypeOrTransport );
9089				return false;
9090			} else if ( seekingTransport ) {
9091				return !( selected = dataTypeOrTransport );
9092			}
9093		} );
9094		return selected;
9095	}
9096
9097	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
9098}
9099
9100// A special extend for ajax options
9101// that takes "flat" options (not to be deep extended)
9102// Fixes #9887
9103function ajaxExtend( target, src ) {
9104	var key, deep,
9105		flatOptions = jQuery.ajaxSettings.flatOptions || {};
9106
9107	for ( key in src ) {
9108		if ( src[ key ] !== undefined ) {
9109			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
9110		}
9111	}
9112	if ( deep ) {
9113		jQuery.extend( true, target, deep );
9114	}
9115
9116	return target;
9117}
9118
9119/* Handles responses to an ajax request:
9120 * - finds the right dataType (mediates between content-type and expected dataType)
9121 * - returns the corresponding response
9122 */
9123function ajaxHandleResponses( s, jqXHR, responses ) {
9124
9125	var ct, type, finalDataType, firstDataType,
9126		contents = s.contents,
9127		dataTypes = s.dataTypes;
9128
9129	// Remove auto dataType and get content-type in the process
9130	while ( dataTypes[ 0 ] === "*" ) {
9131		dataTypes.shift();
9132		if ( ct === undefined ) {
9133			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
9134		}
9135	}
9136
9137	// Check if we're dealing with a known content-type
9138	if ( ct ) {
9139		for ( type in contents ) {
9140			if ( contents[ type ] && contents[ type ].test( ct ) ) {
9141				dataTypes.unshift( type );
9142				break;
9143			}
9144		}
9145	}
9146
9147	// Check to see if we have a response for the expected dataType
9148	if ( dataTypes[ 0 ] in responses ) {
9149		finalDataType = dataTypes[ 0 ];
9150	} else {
9151
vendor: 6,163 bytes, lines 9152-9419
9152		// Try convertible dataTypes
9153		for ( type in responses ) {
9154			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
9155				finalDataType = type;
9156				break;
9157			}
9158			if ( !firstDataType ) {
9159				firstDataType = type;
9160			}
9161		}
9162
9163		// Or just use first one
9164		finalDataType = finalDataType || firstDataType;
9165	}
9166
9167	// If we found a dataType
9168	// We add the dataType to the list if needed
9169	// and return the corresponding response
9170	if ( finalDataType ) {
9171		if ( finalDataType !== dataTypes[ 0 ] ) {
9172			dataTypes.unshift( finalDataType );
9173		}
9174		return responses[ finalDataType ];
9175	}
9176}
9177
9178/* Chain conversions given the request and the original response
9179 * Also sets the responseXXX fields on the jqXHR instance
9180 */
9181function ajaxConvert( s, response, jqXHR, isSuccess ) {
9182	var conv2, current, conv, tmp, prev,
9183		converters = {},
9184
9185		// Work with a copy of dataTypes in case we need to modify it for conversion
9186		dataTypes = s.dataTypes.slice();
9187
9188	// Create converters map with lowercased keys
9189	if ( dataTypes[ 1 ] ) {
9190		for ( conv in s.converters ) {
9191			converters[ conv.toLowerCase() ] = s.converters[ conv ];
9192		}
9193	}
9194
9195	current = dataTypes.shift();
9196
9197	// Convert to each sequential dataType
9198	while ( current ) {
9199
9200		if ( s.responseFields[ current ] ) {
9201			jqXHR[ s.responseFields[ current ] ] = response;
9202		}
9203
9204		// Apply the dataFilter if provided
9205		if ( !prev && isSuccess && s.dataFilter ) {
9206			response = s.dataFilter( response, s.dataType );
9207		}
9208
9209		prev = current;
9210		current = dataTypes.shift();
9211
9212		if ( current ) {
9213
9214			// There's only work to do if current dataType is non-auto
9215			if ( current === "*" ) {
9216
9217				current = prev;
9218
9219			// Convert response if prev dataType is non-auto and differs from current
9220			} else if ( prev !== "*" && prev !== current ) {
9221
9222				// Seek a direct converter
9223				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
9224
9225				// If none found, seek a pair
9226				if ( !conv ) {
9227					for ( conv2 in converters ) {
9228
9229						// If conv2 outputs current
9230						tmp = conv2.split( " " );
9231						if ( tmp[ 1 ] === current ) {
9232
9233							// If prev can be converted to accepted input
9234							conv = converters[ prev + " " + tmp[ 0 ] ] ||
9235								converters[ "* " + tmp[ 0 ] ];
9236							if ( conv ) {
9237
9238								// Condense equivalence converters
9239								if ( conv === true ) {
9240									conv = converters[ conv2 ];
9241
9242								// Otherwise, insert the intermediate dataType
9243								} else if ( converters[ conv2 ] !== true ) {
9244									current = tmp[ 0 ];
9245									dataTypes.unshift( tmp[ 1 ] );
9246								}
9247								break;
9248							}
9249						}
9250					}
9251				}
9252
9253				// Apply converter (if not an equivalence)
9254				if ( conv !== true ) {
9255
9256					// Unless errors are allowed to bubble, catch and return them
9257					if ( conv && s.throws ) {
9258						response = conv( response );
9259					} else {
9260						try {
9261							response = conv( response );
9262						} catch ( e ) {
9263							return {
9264								state: "parsererror",
9265								error: conv ? e : "No conversion from " + prev + " to " + current
9266							};
9267						}
9268					}
9269				}
9270			}
9271		}
9272	}
9273
9274	return { state: "success", data: response };
9275}
9276
9277jQuery.extend( {
9278
9279	// Counter for holding the number of active queries
9280	active: 0,
9281
9282	// Last-Modified header cache for next request
9283	lastModified: {},
9284	etag: {},
9285
9286	ajaxSettings: {
9287		url: location.href,
9288		type: "GET",
9289		isLocal: rlocalProtocol.test( location.protocol ),
9290		global: true,
9291		processData: true,
9292		async: true,
9293		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
9294
9295		/*
9296		timeout: 0,
9297		data: null,
9298		dataType: null,
9299		username: null,
9300		password: null,
9301		cache: null,
9302		throws: false,
9303		traditional: false,
9304		headers: {},
9305		*/
9306
9307		accepts: {
9308			"*": allTypes,
9309			text: "text/plain",
9310			html: "text/html",
9311			xml: "application/xml, text/xml",
9312			json: "application/json, text/javascript"
9313		},
9314
9315		contents: {
9316			xml: /\bxml\b/,
9317			html: /\bhtml/,
9318			json: /\bjson\b/
9319		},
9320
9321		responseFields: {
9322			xml: "responseXML",
9323			text: "responseText",
9324			json: "responseJSON"
9325		},
9326
9327		// Data converters
9328		// Keys separate source (or catchall "*") and destination types with a single space
9329		converters: {
9330
9331			// Convert anything to text
9332			"* text": String,
9333
9334			// Text to html (true = no transformation)
9335			"text html": true,
9336
9337			// Evaluate text as a json expression
9338			"text json": JSON.parse,
9339
9340			// Parse text as xml
9341			"text xml": jQuery.parseXML
9342		},
9343
9344		// For options that shouldn't be deep extended:
9345		// you can add your own custom options here if
9346		// and when you create one that shouldn't be
9347		// deep extended (see ajaxExtend)
9348		flatOptions: {
9349			url: true,
9350			context: true
9351		}
9352	},
9353
9354	// Creates a full fledged settings object into target
9355	// with both ajaxSettings and settings fields.
9356	// If target is omitted, writes into ajaxSettings.
9357	ajaxSetup: function( target, settings ) {
9358		return settings ?
9359
9360			// Building a settings object
9361			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
9362
9363			// Extending ajaxSettings
9364			ajaxExtend( jQuery.ajaxSettings, target );
9365	},
9366
9367	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
9368	ajaxTransport: addToPrefiltersOrTransports( transports ),
9369
9370	// Main method
9371	ajax: function( url, options ) {
9372
9373		// If url is an object, simulate pre-1.5 signature
9374		if ( typeof url === "object" ) {
9375			options = url;
9376			url = undefined;
9377		}
9378
9379		// Force options to be an object
9380		options = options || {};
9381
9382		var transport,
9383
9384			// URL without anti-cache param
9385			cacheURL,
9386
9387			// Response headers
9388			responseHeadersString,
9389			responseHeaders,
9390
9391			// timeout handle
9392			timeoutTimer,
9393
9394			// Url cleanup var
9395			urlAnchor,
9396
9397			// Request state (becomes false upon send and true upon completion)
9398			completed,
9399
9400			// To know if global events are to be dispatched
9401			fireGlobals,
9402
9403			// Loop variable
9404			i,
9405
9406			// uncached part of the url
9407			uncached,
9408
9409			// Create the final options object
9410			s = jQuery.ajaxSetup( {}, options ),
9411
9412			// Callbacks context
9413			callbackContext = s.context || s,
9414
9415			// Context for global events is callbackContext if it is a DOM node or jQuery collection
9416			globalEventContext = s.context &&
9417				( callbackContext.nodeType || callbackContext.jquery ) ?
9418					jQuery( callbackContext ) :
9419	
vendor: 8,850 bytes, lines 9419-9731
9419				jQuery.event,
9420
9421			// Deferreds
9422			deferred = jQuery.Deferred(),
9423			completeDeferred = jQuery.Callbacks( "once memory" ),
9424
9425			// Status-dependent callbacks
9426			statusCode = s.statusCode || {},
9427
9428			// Headers (they are sent all at once)
9429			requestHeaders = {},
9430			requestHeadersNames = {},
9431
9432			// Default abort message
9433			strAbort = "canceled",
9434
9435			// Fake xhr
9436			jqXHR = {
9437				readyState: 0,
9438
9439				// Builds headers hashtable if needed
9440				getResponseHeader: function( key ) {
9441					var match;
9442					if ( completed ) {
9443						if ( !responseHeaders ) {
9444							responseHeaders = {};
9445							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
9446								responseHeaders[ match[ 1 ].toLowerCase() + " " ] =
9447									( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] )
9448										.concat( match[ 2 ] );
9449							}
9450						}
9451						match = responseHeaders[ key.toLowerCase() + " " ];
9452					}
9453					return match == null ? null : match.join( ", " );
9454				},
9455
9456				// Raw string
9457				getAllResponseHeaders: function() {
9458					return completed ? responseHeadersString : null;
9459				},
9460
9461				// Caches the header
9462				setRequestHeader: function( name, value ) {
9463					if ( completed == null ) {
9464						name = requestHeadersNames[ name.toLowerCase() ] =
9465							requestHeadersNames[ name.toLowerCase() ] || name;
9466						requestHeaders[ name ] = value;
9467					}
9468					return this;
9469				},
9470
9471				// Overrides response content-type header
9472				overrideMimeType: function( type ) {
9473					if ( completed == null ) {
9474						s.mimeType = type;
9475					}
9476					return this;
9477				},
9478
9479				// Status-dependent callbacks
9480				statusCode: function( map ) {
9481					var code;
9482					if ( map ) {
9483						if ( completed ) {
9484
9485							// Execute the appropriate callbacks
9486							jqXHR.always( map[ jqXHR.status ] );
9487						} else {
9488
9489							// Lazy-add the new callbacks in a way that preserves old ones
9490							for ( code in map ) {
9491								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
9492							}
9493						}
9494					}
9495					return this;
9496				},
9497
9498				// Cancel the request
9499				abort: function( statusText ) {
9500					var finalText = statusText || strAbort;
9501					if ( transport ) {
9502						transport.abort( finalText );
9503					}
9504					done( 0, finalText );
9505					return this;
9506				}
9507			};
9508
9509		// Attach deferreds
9510		deferred.promise( jqXHR );
9511
9512		// Add protocol if not provided (prefilters might expect it)
9513		// Handle falsy url in the settings object (#10093: consistency with old signature)
9514		// We also use the url parameter if available
9515		s.url = ( ( url || s.url || location.href ) + "" )
9516			.replace( rprotocol, location.protocol + "//" );
9517
9518		// Alias method option to type as per ticket #12004
9519		s.type = options.method || options.type || s.method || s.type;
9520
9521		// Extract dataTypes list
9522		s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
9523
9524		// A cross-domain request is in order when the origin doesn't match the current origin.
9525		if ( s.crossDomain == null ) {
9526			urlAnchor = document.createElement( "a" );
9527
9528			// Support: IE <=8 - 11, Edge 12 - 15
9529			// IE throws exception on accessing the href property if url is malformed,
9530			// e.g. http://example.com:80x/
9531			try {
9532				urlAnchor.href = s.url;
9533
9534				// Support: IE <=8 - 11 only
9535				// Anchor's host property isn't correctly set when s.url is relative
9536				urlAnchor.href = urlAnchor.href;
9537				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
9538					urlAnchor.protocol + "//" + urlAnchor.host;
9539			} catch ( e ) {
9540
9541				// If there is an error parsing the URL, assume it is crossDomain,
9542				// it can be rejected by the transport if it is invalid
9543				s.crossDomain = true;
9544			}
9545		}
9546
9547		// Convert data if not already a string
9548		if ( s.data && s.processData && typeof s.data !== "string" ) {
9549			s.data = jQuery.param( s.data, s.traditional );
9550		}
9551
9552		// Apply prefilters
9553		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
9554
9555		// If request was aborted inside a prefilter, stop there
9556		if ( completed ) {
9557			return jqXHR;
9558		}
9559
9560		// We can fire global events as of now if asked to
9561		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
9562		fireGlobals = jQuery.event && s.global;
9563
9564		// Watch for a new set of requests
9565		if ( fireGlobals && jQuery.active++ === 0 ) {
9566			jQuery.event.trigger( "ajaxStart" );
9567		}
9568
9569		// Uppercase the type
9570		s.type = s.type.toUpperCase();
9571
9572		// Determine if request has content
9573		s.hasContent = !rnoContent.test( s.type );
9574
9575		// Save the URL in case we're toying with the If-Modified-Since
9576		// and/or If-None-Match header later on
9577		// Remove hash to simplify url manipulation
9578		cacheURL = s.url.replace( rhash, "" );
9579
9580		// More options handling for requests with no content
9581		if ( !s.hasContent ) {
9582
9583			// Remember the hash so we can put it back
9584			uncached = s.url.slice( cacheURL.length );
9585
9586			// If data is available and should be processed, append data to url
9587			if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
9588				cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
9589
9590				// #9682: remove data so that it's not used in an eventual retry
9591				delete s.data;
9592			}
9593
9594			// Add or update anti-cache param if needed
9595			if ( s.cache === false ) {
9596				cacheURL = cacheURL.replace( rantiCache, "$1" );
9597				uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) +
9598					uncached;
9599			}
9600
9601			// Put hash and anti-cache on the URL that will be requested (gh-1732)
9602			s.url = cacheURL + uncached;
9603
9604		// Change '%20' to '+' if this is encoded form body content (gh-2658)
9605		} else if ( s.data && s.processData &&
9606			( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
9607			s.data = s.data.replace( r20, "+" );
9608		}
9609
9610		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
9611		if ( s.ifModified ) {
9612			if ( jQuery.lastModified[ cacheURL ] ) {
9613				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
9614			}
9615			if ( jQuery.etag[ cacheURL ] ) {
9616				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
9617			}
9618		}
9619
9620		// Set the correct header, if data is being sent
9621		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
9622			jqXHR.setRequestHeader( "Content-Type", s.contentType );
9623		}
9624
9625		// Set the Accepts header for the server, depending on the dataType
9626		jqXHR.setRequestHeader(
9627			"Accept",
9628			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
9629				s.accepts[ s.dataTypes[ 0 ] ] +
9630					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
9631				s.accepts[ "*" ]
9632		);
9633
9634		// Check for headers option
9635		for ( i in s.headers ) {
9636			jqXHR.setRequestHeader( i, s.headers[ i ] );
9637		}
9638
9639		// Allow custom headers/mimetypes and early abort
9640		if ( s.beforeSend &&
9641			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
9642
9643			// Abort if not done already and return
9644			return jqXHR.abort();
9645		}
9646
9647		// Aborting is no longer a cancellation
9648		strAbort = "abort";
9649
9650		// Install callbacks on deferreds
9651		completeDeferred.add( s.complete );
9652		jqXHR.done( s.success );
9653		jqXHR.fail( s.error );
9654
9655		// Get transport
9656		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
9657
9658		// If no transport, we auto-abort
9659		if ( !transport ) {
9660			done( -1, "No Transport" );
9661		} else {
9662			jqXHR.readyState = 1;
9663
9664			// Send global event
9665			if ( fireGlobals ) {
9666				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
9667			}
9668
9669			// If request was aborted inside ajaxSend, stop there
9670			if ( completed ) {
9671				return jqXHR;
9672			}
9673
9674			// Timeout
9675			if ( s.async && s.timeout > 0 ) {
9676				timeoutTimer = window.setTimeout( function() {
9677					jqXHR.abort( "timeout" );
9678				}, s.timeout );
9679			}
9680
9681			try {
9682				completed = false;
9683				transport.send( requestHeaders, done );
9684			} catch ( e ) {
9685
9686				// Rethrow post-completion exceptions
9687				if ( completed ) {
9688					throw e;
9689				}
9690
9691				// Propagate others as results
9692				done( -1, e );
9693			}
9694		}
9695
9696		// Callback for when everything is done
9697		function done( status, nativeStatusText, responses, headers ) {
9698			var isSuccess, success, error, response, modified,
9699				statusText = nativeStatusText;
9700
9701			// Ignore repeat invocations
9702			if ( completed ) {
9703				return;
9704			}
9705
9706			completed = true;
9707
9708			// Clear timeout if it exists
9709			if ( timeoutTimer ) {
9710				window.clearTimeout( timeoutTimer );
9711			}
9712
9713			// Dereference transport for early garbage collection
9714			// (no matter how long the jqXHR object will be used)
9715			transport = undefined;
9716
9717			// Cache response headers
9718			responseHeadersString = headers || "";
9719
9720			// Set readyState
9721			jqXHR.readyState = status > 0 ? 4 : 0;
9722
9723			// Determine if successful
9724			isSuccess = status >= 200 && status < 300 || status === 304;
9725
9726			// Get response data
9727			if ( responses ) {
9728				response = ajaxHandleResponses( s, jqXHR, responses );
9729			}
9730
9731			// Use a noop converter for missing script
9731
9732			if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1
vendor: 18,647 bytes, lines 9732-10470
9732 ) {
9733				s.converters[ "text script" ] = function() {};
9734			}
9735
9736			// Convert no matter what (that way responseXXX fields are always set)
9737			response = ajaxConvert( s, response, jqXHR, isSuccess );
9738
9739			// If successful, handle type chaining
9740			if ( isSuccess ) {
9741
9742				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
9743				if ( s.ifModified ) {
9744					modified = jqXHR.getResponseHeader( "Last-Modified" );
9745					if ( modified ) {
9746						jQuery.lastModified[ cacheURL ] = modified;
9747					}
9748					modified = jqXHR.getResponseHeader( "etag" );
9749					if ( modified ) {
9750						jQuery.etag[ cacheURL ] = modified;
9751					}
9752				}
9753
9754				// if no content
9755				if ( status === 204 || s.type === "HEAD" ) {
9756					statusText = "nocontent";
9757
9758				// if not modified
9759				} else if ( status === 304 ) {
9760					statusText = "notmodified";
9761
9762				// If we have data, let's convert it
9763				} else {
9764					statusText = response.state;
9765					success = response.data;
9766					error = response.error;
9767					isSuccess = !error;
9768				}
9769			} else {
9770
9771				// Extract error from statusText and normalize for non-aborts
9772				error = statusText;
9773				if ( status || !statusText ) {
9774					statusText = "error";
9775					if ( status < 0 ) {
9776						status = 0;
9777					}
9778				}
9779			}
9780
9781			// Set data for the fake xhr object
9782			jqXHR.status = status;
9783			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
9784
9785			// Success/Error
9786			if ( isSuccess ) {
9787				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
9788			} else {
9789				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
9790			}
9791
9792			// Status-dependent callbacks
9793			jqXHR.statusCode( statusCode );
9794			statusCode = undefined;
9795
9796			if ( fireGlobals ) {
9797				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
9798					[ jqXHR, s, isSuccess ? success : error ] );
9799			}
9800
9801			// Complete
9802			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
9803
9804			if ( fireGlobals ) {
9805				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
9806
9807				// Handle the global AJAX counter
9808				if ( !( --jQuery.active ) ) {
9809					jQuery.event.trigger( "ajaxStop" );
9810				}
9811			}
9812		}
9813
9814		return jqXHR;
9815	},
9816
9817	getJSON: function( url, data, callback ) {
9818		return jQuery.get( url, data, callback, "json" );
9819	},
9820
9821	getScript: function( url, callback ) {
9822		return jQuery.get( url, undefined, callback, "script" );
9823	}
9824} );
9825
9826jQuery.each( [ "get", "post" ], function( _i, method ) {
9827	jQuery[ method ] = function( url, data, callback, type ) {
9828
9829		// Shift arguments if data argument was omitted
9830		if ( isFunction( data ) ) {
9831			type = type || callback;
9832			callback = data;
9833			data = undefined;
9834		}
9835
9836		// The url can be an options object (which then must have .url)
9837		return jQuery.ajax( jQuery.extend( {
9838			url: url,
9839			type: method,
9840			dataType: type,
9841			data: data,
9842			success: callback
9843		}, jQuery.isPlainObject( url ) && url ) );
9844	};
9845} );
9846
9847jQuery.ajaxPrefilter( function( s ) {
9848	var i;
9849	for ( i in s.headers ) {
9850		if ( i.toLowerCase() === "content-type" ) {
9851			s.contentType = s.headers[ i ] || "";
9852		}
9853	}
9854} );
9855
9856
9857jQuery._evalUrl = function( url, options, doc ) {
9858	return jQuery.ajax( {
9859		url: url,
9860
9861		// Make this explicit, since user can override this through ajaxSetup (#11264)
9862		type: "GET",
9863		dataType: "script",
9864		cache: true,
9865		async: false,
9866		global: false,
9867
9868		// Only evaluate the response if it is successful (gh-4126)
9869		// dataFilter is not invoked for failure responses, so using it instead
9870		// of the default converter is kludgy but it works.
9871		converters: {
9872			"text script": function() {}
9873		},
9874		dataFilter: function( response ) {
9875			jQuery.globalEval( response, options, doc );
9876		}
9877	} );
9878};
9879
9880
9881jQuery.fn.extend( {
9882	wrapAll: function( html ) {
9883		var wrap;
9884
9885		if ( this[ 0 ] ) {
9886			if ( isFunction( html ) ) {
9887				html = html.call( this[ 0 ] );
9888			}
9889
9890			// The elements to wrap the target around
9891			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
9892
9893			if ( this[ 0 ].parentNode ) {
9894				wrap.insertBefore( this[ 0 ] );
9895			}
9896
9897			wrap.map( function() {
9898				var elem = this;
9899
9900				while ( elem.firstElementChild ) {
9901					elem = elem.firstElementChild;
9902				}
9903
9904				return elem;
9905			} ).append( this );
9906		}
9907
9908		return this;
9909	},
9910
9911	wrapInner: function( html ) {
9912		if ( isFunction( html ) ) {
9913			return this.each( function( i ) {
9914				jQuery( this ).wrapInner( html.call( this, i ) );
9915			} );
9916		}
9917
9918		return this.each( function() {
9919			var self = jQuery( this ),
9920				contents = self.contents();
9921
9922			if ( contents.length ) {
9923				contents.wrapAll( html );
9924
9925			} else {
9926				self.append( html );
9927			}
9928		} );
9929	},
9930
9931	wrap: function( html ) {
9932		var htmlIsFunction = isFunction( html );
9933
9934		return this.each( function( i ) {
9935			jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
9936		} );
9937	},
9938
9939	unwrap: function( selector ) {
9940		this.parent( selector ).not( "body" ).each( function() {
9941			jQuery( this ).replaceWith( this.childNodes );
9942		} );
9943		return this;
9944	}
9945} );
9946
9947
9948jQuery.expr.pseudos.hidden = function( elem ) {
9949	return !jQuery.expr.pseudos.visible( elem );
9950};
9951jQuery.expr.pseudos.visible = function( elem ) {
9952	return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
9953};
9954
9955
9956
9957
9958jQuery.ajaxSettings.xhr = function() {
9959	try {
9960		return new window.XMLHttpRequest();
9961	} catch ( e ) {}
9962};
9963
9964var xhrSuccessStatus = {
9965
9966		// File protocol always yields status code 0, assume 200
9967		0: 200,
9968
9969		// Support: IE <=9 only
9970		// #1450: sometimes IE returns 1223 when it should be 204
9971		1223: 204
9972	},
9973	xhrSupported = jQuery.ajaxSettings.xhr();
9974
9975support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
9976support.ajax = xhrSupported = !!xhrSupported;
9977
9978jQuery.ajaxTransport( function( options ) {
9979	var callback, errorCallback;
9980
9981	// Cross domain only allowed if supported through XMLHttpRequest
9982	if ( support.cors || xhrSupported && !options.crossDomain ) {
9983		return {
9984			send: function( headers, complete ) {
9985				var i,
9986					xhr = options.xhr();
9987
9988				xhr.open(
9989					options.type,
9990					options.url,
9991					options.async,
9992					options.username,
9993					options.password
9994				);
9995
9996				// Apply custom fields if provided
9997				if ( options.xhrFields ) {
9998					for ( i in options.xhrFields ) {
9999						xhr[ i ] = options.xhrFields[ i ];
10000					}
10001				}
10002
10003				// Override mime type if needed
10004				if ( options.mimeType && xhr.overrideMimeType ) {
10005					xhr.overrideMimeType( options.mimeType );
10006				}
10007
10008				// X-Requested-With header
10009				// For cross-domain requests, seeing as conditions for a preflight are
10010				// akin to a jigsaw puzzle, we simply never set it to be sure.
10011				// (it can always be set on a per-request basis or even using ajaxSetup)
10012				// For same-domain requests, won't change header if already provided.
10013				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
10014					headers[ "X-Requested-With" ] = "XMLHttpRequest";
10015				}
10016
10017				// Set headers
10018				for ( i in headers ) {
10019					xhr.setRequestHeader( i, headers[ i ] );
10020				}
10021
10022				// Callback
10023				callback = function( type ) {
10024					return function() {
10025						if ( callback ) {
10026							callback = errorCallback = xhr.onload =
10027								xhr.onerror = xhr.onabort = xhr.ontimeout =
10028									xhr.onreadystatechange = null;
10029
10030							if ( type === "abort" ) {
10031								xhr.abort();
10032							} else if ( type === "error" ) {
10033
10034								// Support: IE <=9 only
10035								// On a manual native abort, IE9 throws
10036								// errors on any property access that is not readyState
10037								if ( typeof xhr.status !== "number" ) {
10038									complete( 0, "error" );
10039								} else {
10040									complete(
10041
10042										// File: protocol always yields status 0; see #8605, #14207
10043										xhr.status,
10044										xhr.statusText
10045									);
10046								}
10047							} else {
10048								complete(
10049									xhrSuccessStatus[ xhr.status ] || xhr.status,
10050									xhr.statusText,
10051
10052									// Support: IE <=9 only
10053									// IE9 has no XHR2 but throws on binary (trac-11426)
10054									// For XHR2 non-text, let the caller handle it (gh-2498)
10055									( xhr.responseType || "text" ) !== "text"  ||
10056									typeof xhr.responseText !== "string" ?
10057										{ binary: xhr.response } :
10058										{ text: xhr.responseText },
10059									xhr.getAllResponseHeaders()
10060								);
10061							}
10062						}
10063					};
10064				};
10065
10066				// Listen to events
10067				xhr.onload = callback();
10068				errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );
10069
10070				// Support: IE 9 only
10071				// Use onreadystatechange to replace onabort
10072				// to handle uncaught aborts
10073				if ( xhr.onabort !== undefined ) {
10074					xhr.onabort = errorCallback;
10075				} else {
10076					xhr.onreadystatechange = function() {
10077
10078						// Check readyState before timeout as it changes
10079						if ( xhr.readyState === 4 ) {
10080
10081							// Allow onerror to be called first,
10082							// but that will not handle a native abort
10083							// Also, save errorCallback to a variable
10084							// as xhr.onerror cannot be accessed
10085							window.setTimeout( function() {
10086								if ( callback ) {
10087									errorCallback();
10088								}
10089							} );
10090						}
10091					};
10092				}
10093
10094				// Create the abort callback
10095				callback = callback( "abort" );
10096
10097				try {
10098
10099					// Do send the request (this may raise an exception)
10100					xhr.send( options.hasContent && options.data || null );
10101				} catch ( e ) {
10102
10103					// #14683: Only rethrow if this hasn't been notified as an error yet
10104					if ( callback ) {
10105						throw e;
10106					}
10107				}
10108			},
10109
10110			abort: function() {
10111				if ( callback ) {
10112					callback();
10113				}
10114			}
10115		};
10116	}
10117} );
10118
10119
10120
10121
10122// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
10123jQuery.ajaxPrefilter( function( s ) {
10124	if ( s.crossDomain ) {
10125		s.contents.script = false;
10126	}
10127} );
10128
10129// Install script dataType
10130jQuery.ajaxSetup( {
10131	accepts: {
10132		script: "text/javascript, application/javascript, " +
10133			"application/ecmascript, application/x-ecmascript"
10134	},
10135	contents: {
10136		script: /\b(?:java|ecma)script\b/
10137	},
10138	converters: {
10139		"text script": function( text ) {
10140			jQuery.globalEval( text );
10141			return text;
10142		}
10143	}
10144} );
10145
10146// Handle cache's special case and crossDomain
10147jQuery.ajaxPrefilter( "script", function( s ) {
10148	if ( s.cache === undefined ) {
10149		s.cache = false;
10150	}
10151	if ( s.crossDomain ) {
10152		s.type = "GET";
10153	}
10154} );
10155
10156// Bind script tag hack transport
10157jQuery.ajaxTransport( "script", function( s ) {
10158
10159	// This transport only deals with cross domain or forced-by-attrs requests
10160	if ( s.crossDomain || s.scriptAttrs ) {
10161		var script, callback;
10162		return {
10163			send: function( _, complete ) {
10164				script = jQuery( "<script>" )
10165					.attr( s.scriptAttrs || {} )
10166					.prop( { charset: s.scriptCharset, src: s.url } )
10167					.on( "load error", callback = function( evt ) {
10168						script.remove();
10169						callback = null;
10170						if ( evt ) {
10171							complete( evt.type === "error" ? 404 : 200, evt.type );
10172						}
10173					} );
10174
10175				// Use native DOM manipulation to avoid our domManip AJAX trickery
10176				document.head.appendChild( script[ 0 ] );
10177			},
10178			abort: function() {
10179				if ( callback ) {
10180					callback();
10181				}
10182			}
10183		};
10184	}
10185} );
10186
10187
10188
10189
10190var oldCallbacks = [],
10191	rjsonp = /(=)\?(?=&|$)|\?\?/;
10192
10193// Default jsonp settings
10194jQuery.ajaxSetup( {
10195	jsonp: "callback",
10196	jsonpCallback: function() {
10197		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) );
10198		this[ callback ] = true;
10199		return callback;
10200	}
10201} );
10202
10203// Detect, normalize options and install callbacks for jsonp requests
10204jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
10205
10206	var callbackName, overwritten, responseContainer,
10207		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
10208			"url" :
10209			typeof s.data === "string" &&
10210				( s.contentType || "" )
10211					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
10212				rjsonp.test( s.data ) && "data"
10213		);
10214
10215	// Handle iff the expected data type is "jsonp" or we have a parameter to set
10216	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
10217
10218		// Get callback name, remembering preexisting value associated with it
10219		callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
10220			s.jsonpCallback() :
10221			s.jsonpCallback;
10222
10223		// Insert callback into url or form data
10224		if ( jsonProp ) {
10225			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
10226		} else if ( s.jsonp !== false ) {
10227			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
10228		}
10229
10230		// Use data converter to retrieve json after script execution
10231		s.converters[ "script json" ] = function() {
10232			if ( !responseContainer ) {
10233				jQuery.error( callbackName + " was not called" );
10234			}
10235			return responseContainer[ 0 ];
10236		};
10237
10238		// Force json dataType
10239		s.dataTypes[ 0 ] = "json";
10240
10241		// Install callback
10242		overwritten = window[ callbackName ];
10243		window[ callbackName ] = function() {
10244			responseContainer = arguments;
10245		};
10246
10247		// Clean-up function (fires after converters)
10248		jqXHR.always( function() {
10249
10250			// If previous value didn't exist - remove it
10251			if ( overwritten === undefined ) {
10252				jQuery( window ).removeProp( callbackName );
10253
10254			// Otherwise restore preexisting value
10255			} else {
10256				window[ callbackName ] = overwritten;
10257			}
10258
10259			// Save back as free
10260			if ( s[ callbackName ] ) {
10261
10262				// Make sure that re-using the options doesn't screw things around
10263				s.jsonpCallback = originalSettings.jsonpCallback;
10264
10265				// Save the callback name for future use
10266				oldCallbacks.push( callbackName );
10267			}
10268
10269			// Call if it was a function and we have a response
10270			if ( responseContainer && isFunction( overwritten ) ) {
10271				overwritten( responseContainer[ 0 ] );
10272			}
10273
10274			responseContainer = overwritten = undefined;
10275		} );
10276
10277		// Delegate to script
10278		return "script";
10279	}
10280} );
10281
10282
10283
10284
10285// Support: Safari 8 only
10286// In Safari 8 documents created via document.implementation.createHTMLDocument
10287// collapse sibling forms: the second one becomes a child of the first one.
10288// Because of that, this security measure has to be disabled in Safari 8.
10289// https://bugs.webkit.org/show_bug.cgi?id=137337
10290support.createHTMLDocument = ( function() {
10291	var body = document.implementation.createHTMLDocument( "" ).body;
10292	body.innerHTML = "<form></form><form></form>";
10293	return body.childNodes.length === 2;
10294} )();
10295
10296
10297// Argument "data" should be string of html
10298// context (optional): If specified, the fragment will be created in this context,
10299// defaults to document
10300// keepScripts (optional): If true, will include scripts passed in the html string
10301jQuery.parseHTML = function( data, context, keepScripts ) {
10302	if ( typeof data !== "string" ) {
10303		return [];
10304	}
10305	if ( typeof context === "boolean" ) {
10306		keepScripts = context;
10307		context = false;
10308	}
10309
10310	var base, parsed, scripts;
10311
10312	if ( !context ) {
10313
10314		// Stop scripts or inline event handlers from being executed immediately
10315		// by using document.implementation
10316		if ( support.createHTMLDocument ) {
10317			context = document.implementation.createHTMLDocument( "" );
10318
10319			// Set the base href for the created document
10320			// so any parsed elements with URLs
10321			// are based on the document's URL (gh-2965)
10322			base = context.createElement( "base" );
10323			base.href = document.location.href;
10324			context.head.appendChild( base );
10325		} else {
10326			context = document;
10327		}
10328	}
10329
10330	parsed = rsingleTag.exec( data );
10331	scripts = !keepScripts && [];
10332
10333	// Single tag
10334	if ( parsed ) {
10335		return [ context.createElement( parsed[ 1 ] ) ];
10336	}
10337
10338	parsed = buildFragment( [ data ], context, scripts );
10339
10340	if ( scripts && scripts.length ) {
10341		jQuery( scripts ).remove();
10342	}
10343
10344	return jQuery.merge( [], parsed.childNodes );
10345};
10346
10347
10348/**
10349 * Load a url into a page
10350 */
10351jQuery.fn.load = function( url, params, callback ) {
10352	var selector, type, response,
10353		self = this,
10354		off = url.indexOf( " " );
10355
10356	if ( off > -1 ) {
10357		selector = stripAndCollapse( url.slice( off ) );
10358		url = url.slice( 0, off );
10359	}
10360
10361	// If it's a function
10362	if ( isFunction( params ) ) {
10363
10364		// We assume that it's the callback
10365		callback = params;
10366		params = undefined;
10367
10368	// Otherwise, build a param string
10369	} else if ( params && typeof params === "object" ) {
10370		type = "POST";
10371	}
10372
10373	// If we have elements to modify, make the request
10374	if ( self.length > 0 ) {
10375		jQuery.ajax( {
10376			url: url,
10377
10378			// If "type" variable is undefined, then "GET" method will be used.
10379			// Make value of this field explicit since
10380			// user can override it through ajaxSetup method
10381			type: type || "GET",
10382			dataType: "html",
10383			data: params
10384		} ).done( function( responseText ) {
10385
10386			// Save response for use in complete callback
10387			response = arguments;
10388
10389			self.html( selector ?
10390
10391				// If a selector was specified, locate the right elements in a dummy div
10392				// Exclude scripts to avoid IE 'Permission Denied' errors
10393				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
10394
10395				// Otherwise use the full result
10396				responseText );
10397
10398		// If the request succeeds, this function gets "data", "status", "jqXHR"
10399		// but they are ignored because response was set above.
10400		// If it fails, this function gets "jqXHR", "status", "error"
10401		} ).always( callback && function( jqXHR, status ) {
10402			self.each( function() {
10403				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
10404			} );
10405		} );
10406	}
10407
10408	return this;
10409};
10410
10411
10412
10413
10414jQuery.expr.pseudos.animated = function( elem ) {
10415	return jQuery.grep( jQuery.timers, function( fn ) {
10416		return elem === fn.elem;
10417	} ).length;
10418};
10419
10420
10421
10422
10423jQuery.offset = {
10424	setOffset: function( elem, options, i ) {
10425		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
10426			position = jQuery.css( elem, "position" ),
10427			curElem = jQuery( elem ),
10428			props = {};
10429
10430		// Set position first, in-case top/left are set even on static elem
10431		if ( position === "static" ) {
10432			elem.style.position = "relative";
10433		}
10434
10435		curOffset = curElem.offset();
10436		curCSSTop = jQuery.css( elem, "top" );
10437		curCSSLeft = jQuery.css( elem, "left" );
10438		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
10439			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
10440
10441		// Need to be able to calculate position if either
10442		// top or left is auto and position is either absolute or fixed
10443		if ( calculatePosition ) {
10444			curPosition = curElem.position();
10445			curTop = curPosition.top;
10446			curLeft = curPosition.left;
10447
10448		} else {
10449			curTop = parseFloat( curCSSTop ) || 0;
10450			curLeft = parseFloat( curCSSLeft ) || 0;
10451		}
10452
10453		if ( isFunction( options ) ) {
10454
10455			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
10456			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
10457		}
10458
10459		if ( options.top != null ) {
10460			props.top = ( options.top - curOffset.top ) + curTop;
10461		}
10462		if ( options.left != null ) {
10463			props.left = ( options.left - curOffset.left ) + curLeft;
10464		}
10465
10466		if ( "using" in options ) {
10467			options.using.call( elem, props );
10468
10469		} else {
10470			
vendor: 2,160 bytes, lines 10470-10548
10470if ( typeof props.top === "number" ) {
10471				props.top += "px";
10472			}
10473			if ( typeof props.left === "number" ) {
10474				props.left += "px";
10475			}
10476			curElem.css( props );
10477		}
10478	}
10479};
10480
10481jQuery.fn.extend( {
10482
10483	// offset() relates an element's border box to the document origin
10484	offset: function( options ) {
10485
10486		// Preserve chaining for setter
10487		if ( arguments.length ) {
10488			return options === undefined ?
10489				this :
10490				this.each( function( i ) {
10491					jQuery.offset.setOffset( this, options, i );
10492				} );
10493		}
10494
10495		var rect, win,
10496			elem = this[ 0 ];
10497
10498		if ( !elem ) {
10499			return;
10500		}
10501
10502		// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
10503		// Support: IE <=11 only
10504		// Running getBoundingClientRect on a
10505		// disconnected node in IE throws an error
10506		if ( !elem.getClientRects().length ) {
10507			return { top: 0, left: 0 };
10508		}
10509
10510		// Get document-relative position by adding viewport scroll to viewport-relative gBCR
10511		rect = elem.getBoundingClientRect();
10512		win = elem.ownerDocument.defaultView;
10513		return {
10514			top: rect.top + win.pageYOffset,
10515			left: rect.left + win.pageXOffset
10516		};
10517	},
10518
10519	// position() relates an element's margin box to its offset parent's padding box
10520	// This corresponds to the behavior of CSS absolute positioning
10521	position: function() {
10522		if ( !this[ 0 ] ) {
10523			return;
10524		}
10525
10526		var offsetParent, offset, doc,
10527			elem = this[ 0 ],
10528			parentOffset = { top: 0, left: 0 };
10529
10530		// position:fixed elements are offset from the viewport, which itself always has zero offset
10531		if ( jQuery.css( elem, "position" ) === "fixed" ) {
10532
10533			// Assume position:fixed implies availability of getBoundingClientRect
10534			offset = elem.getBoundingClientRect();
10535
10536		} else {
10537			offset = this.offset();
10538
10539			// Account for the *real* offset parent, which can be the document or its root element
10540			// when a statically positioned element is identified
10541			doc = elem.ownerDocument;
10542			offsetParent = elem.offsetParent || doc.documentElement;
10543			while ( offsetParent &&
10544				( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
10545				jQuery.css( offsetParent, "position" ) === "static" ) {
10546
10547				offsetParent = offsetParent.parentNode;
10548			}
vendor: 7,719 bytes, lines 10549-10814
10549			if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
10550
10551				// Incorporate borders into its offset, since they are outside its content origin
10552				parentOffset = jQuery( offsetParent ).offset();
10553				parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
10554				parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
10555			}
10556		}
10557
10558		// Subtract parent offsets and element margins
10559		return {
10560			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
10561			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
10562		};
10563	},
10564
10565	// This method will return documentElement in the following cases:
10566	// 1) For the element inside the iframe without offsetParent, this method will return
10567	//    documentElement of the parent window
10568	// 2) For the hidden or detached element
10569	// 3) For body or html element, i.e. in case of the html node - it will return itself
10570	//
10571	// but those exceptions were never presented as a real life use-cases
10572	// and might be considered as more preferable results.
10573	//
10574	// This logic, however, is not guaranteed and can change at any point in the future
10575	offsetParent: function() {
10576		return this.map( function() {
10577			var offsetParent = this.offsetParent;
10578
10579			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
10580				offsetParent = offsetParent.offsetParent;
10581			}
10582
10583			return offsetParent || documentElement;
10584		} );
10585	}
10586} );
10587
10588// Create scrollLeft and scrollTop methods
10589jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
10590	var top = "pageYOffset" === prop;
10591
10592	jQuery.fn[ method ] = function( val ) {
10593		return access( this, function( elem, method, val ) {
10594
10595			// Coalesce documents and windows
10596			var win;
10597			if ( isWindow( elem ) ) {
10598				win = elem;
10599			} else if ( elem.nodeType === 9 ) {
10600				win = elem.defaultView;
10601			}
10602
10603			if ( val === undefined ) {
10604				return win ? win[ prop ] : elem[ method ];
10605			}
10606
10607			if ( win ) {
10608				win.scrollTo(
10609					!top ? val : win.pageXOffset,
10610					top ? val : win.pageYOffset
10611				);
10612
10613			} else {
10614				elem[ method ] = val;
10615			}
10616		}, method, val, arguments.length );
10617	};
10618} );
10619
10620// Support: Safari <=7 - 9.1, Chrome <=37 - 49
10621// Add the top/left cssHooks using jQuery.fn.position
10622// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
10623// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
10624// getComputedStyle returns percent when specified for top/left/bottom/right;
10625// rather than make the css module depend on the offset module, just check for it here
10626jQuery.each( [ "top", "left" ], function( _i, prop ) {
10627	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
10628		function( elem, computed ) {
10629			if ( computed ) {
10630				computed = curCSS( elem, prop );
10631
10632				// If curCSS returns percentage, fallback to offset
10633				return rnumnonpx.test( computed ) ?
10634					jQuery( elem ).position()[ prop ] + "px" :
10635					computed;
10636			}
10637		}
10638	);
10639} );
10640
10641
10642// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
10643jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
10644	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
10645		function( defaultExtra, funcName ) {
10646
10647		// Margin is only for outerHeight, outerWidth
10648		jQuery.fn[ funcName ] = function( margin, value ) {
10649			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
10650				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
10651
10652			return access( this, function( elem, type, value ) {
10653				var doc;
10654
10655				if ( isWindow( elem ) ) {
10656
10657					// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
10658					return funcName.indexOf( "outer" ) === 0 ?
10659						elem[ "inner" + name ] :
10660						elem.document.documentElement[ "client" + name ];
10661				}
10662
10663				// Get document width or height
10664				if ( elem.nodeType === 9 ) {
10665					doc = elem.documentElement;
10666
10667					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
10668					// whichever is greatest
10669					return Math.max(
10670						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
10671						elem.body[ "offset" + name ], doc[ "offset" + name ],
10672						doc[ "client" + name ]
10673					);
10674				}
10675
10676				return value === undefined ?
10677
10678					// Get width or height on the element, requesting but not forcing parseFloat
10679					jQuery.css( elem, type, extra ) :
10680
10681					// Set width or height on the element
10682					jQuery.style( elem, type, value, extra );
10683			}, type, chainable ? margin : undefined, chainable );
10684		};
10685	} );
10686} );
10687
10688
10689jQuery.each( [
10690	"ajaxStart",
10691	"ajaxStop",
10692	"ajaxComplete",
10693	"ajaxError",
10694	"ajaxSuccess",
10695	"ajaxSend"
10696], function( _i, type ) {
10697	jQuery.fn[ type ] = function( fn ) {
10698		return this.on( type, fn );
10699	};
10700} );
10701
10702
10703
10704
10705jQuery.fn.extend( {
10706
10707	bind: function( types, data, fn ) {
10708		return this.on( types, null, data, fn );
10709	},
10710	unbind: function( types, fn ) {
10711		return this.off( types, null, fn );
10712	},
10713
10714	delegate: function( selector, types, data, fn ) {
10715		return this.on( types, selector, data, fn );
10716	},
10717	undelegate: function( selector, types, fn ) {
10718
10719		// ( namespace ) or ( selector, types [, fn] )
10720		return arguments.length === 1 ?
10721			this.off( selector, "**" ) :
10722			this.off( types, selector || "**", fn );
10723	},
10724
10725	hover: function( fnOver, fnOut ) {
10726		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
10727	}
10728} );
10729
10730jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
10731	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
10732	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
10733	function( _i, name ) {
10734
10735		// Handle event binding
10736		jQuery.fn[ name ] = function( data, fn ) {
10737			return arguments.length > 0 ?
10738				this.on( name, null, data, fn ) :
10739				this.trigger( name );
10740		};
10741	} );
10742
10743
10744
10745
10746// Support: Android <=4.0 only
10747// Make sure we trim BOM and NBSP
10748var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
10749
10750// Bind a function to a context, optionally partially applying any
10751// arguments.
10752// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
10753// However, it is not slated for removal any time soon
10754jQuery.proxy = function( fn, context ) {
10755	var tmp, args, proxy;
10756
10757	if ( typeof context === "string" ) {
10758		tmp = fn[ context ];
10759		context = fn;
10760		fn = tmp;
10761	}
10762
10763	// Quick check to determine if target is callable, in the spec
10764	// this throws a TypeError, but we will just return undefined.
10765	if ( !isFunction( fn ) ) {
10766		return undefined;
10767	}
10768
10769	// Simulated bind
10770	args = slice.call( arguments, 2 );
10771	proxy = function() {
10772		return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
10773	};
10774
10775	// Set the guid of unique handler to the same of original handler, so it can be removed
10776	proxy.guid = fn.guid = fn.guid || jQuery.guid++;
10777
10778	return proxy;
10779};
10780
10781jQuery.holdReady = function( hold ) {
10782	if ( hold ) {
10783		jQuery.readyWait++;
10784	} else {
10785		jQuery.ready( true );
10786	}
10787};
10788jQuery.isArray = Array.isArray;
10789jQuery.parseJSON = JSON.parse;
10790jQuery.nodeName = nodeName;
10791jQuery.isFunction = isFunction;
10792jQuery.isWindow = isWindow;
10793jQuery.camelCase = camelCase;
10794jQuery.type = toType;
10795
10796jQuery.now = Date.now;
10797
10798jQuery.isNumeric = function( obj ) {
10799
10800	// As of jQuery 3.0, isNumeric is limited to
10801	// strings and numbers (primitives or objects)
10802	// that can be coerced to finite numbers (gh-2662)
10803	var type = jQuery.type( obj );
10804	return ( type === "number" || type === "string" ) &&
10805
10806		// parseFloat NaNs numeric-cast false positives ("")
10807		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
10808		// subtraction forces infinities to NaN
10809		!isNaN( obj - parseFloat( obj ) );
10810};
10811
10812jQuery.trim = function( text ) {
10813	return text == null ?
10814		"" :
10815		( text + "" ).replace( rtrim, "" );
10816};
10817
10818
10819
10820// Register as a named AMD module, since jQuery can be concatenated with other
10821// files that may use define, but not via a proper concatenation script that
10822// understands anonymous AMD modules. A named AMD is safest and most robust
10823// way to register. Lowercase jquery is used because AMD module names are
10824// derived from file names, and jQuery is normally delivered in a lowercase
10825// file name. Do this after creating the global so that if an AMD module wants
10826// to call noConflict to hide this version of jQuery, it will work.
10827
10828// Note that for maximum portability, libraries that are not jQuery should
10829// declare themselves as anonymous modules, and avoid setting a global if an
10830// AMD loader is present. jQuery is a special case. For more information, see
10831// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
10832
10833if ( typeof define === "function" && define.amd ) {
10834	define( "jquery", [], function() {
10835		return jQuery;
10836	} );
10837}
10838
10839
10840
10841
10842var
10843
10844	// Map over jQuery in case of overwrite
10845	_jQuery = window.jQuery,
10846
10847	// Map over the $ in case of overwrite
10848	_$ = window.$;
10849
10850jQuery.noConflict = function( deep ) {
10851	if ( window.$ === jQuery ) {
10852		window.$ = _$;
10853	}
10854
10855	if ( deep && window.jQuery === jQuery ) {
10856		window.jQuery = _jQuery;
10857	}
10858
10859	return jQuery;
10860};
10861
10862// Expose jQuery and $ identifiers, even in AMD
10863// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
10864// and CommonJS for browser emulators (#13566)
10865if ( typeof noGlobal === "undefined" ) {
10866	window.jQuery = window.$ = jQuery;
10867}
10868
10869
10870
10871
10872return jQuery;
10873} );
10874/*
10875Unobtrusive JavaScript
10876https://github.com/rails/rails/blob/main/actionview/app/javascript
10877Released under the MIT license
10878
10879Local copy under app/assets/javascripts/ shadows the actionview gem version
10880(Sprockets path precedence). Wrapped in an idempotency guard because base.js
10881(which `//= require rails-ujs`s this file) is required by multiple top-level
10882Sprockets manifests (e.g. a page bundle and teams.js), and pages that load more
10883than one such manifest — e.g. the old race hub layout loading both
10884the hub bundle and `teams` — eval rails-ujs twice. The gem version auto-calls
10885Rails.start() at eval time, which throws "rails-ujs has already been loaded!"
10886on the second eval, a synchronous top-level error that aborts every script
10887after it in that compiled bundle (killing require_tree ./teams — meetup
10888photo-upload/gallery wiring never runs).
10889
10890This same guard shipped once before (commit 73d981a9f, for application.js +
10891fitness_tracker.js on training plan pages) and was reverted (706b66ca1) —
10892but only because that specific page pair was restructured to stop double
10893including base.js (a leaner fitness_tracker/training_plans.js bundle that
10894omits base entirely). That structural per-page fix doesn't scale: any two
10895co-loaded manifests that each `//= require base` reproduce this exact throw,
10896which is what happened again here with the hub bundle + teams. Re-vendoring the
10897guard is the systemic fix — it makes a second rails-ujs eval a no-op instead
10898of a throw, for every current and future co-loaded bundle pair, not just one
10899layout.
10900
10901Keep this file in sync with the gem version on Rails upgrades — diff it,
10902preserve only the outer guard.
10903 */
10904
10905if (typeof window === 'undefined' || !window._rails_loaded) {
10906(function(global, factory) {
10907  typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, 
10908  global.Rails = factory());
10909})(this, (function() {
10910  "use strict";
10911  const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]";
10912  const buttonClickSelector = {
10913    selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])",
10914    exclude: "form button"
10915  };
10916  const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]";
10917  const formSubmitSelector = "form:not([data-turbo=true])";
10918  const formInputClickSelector = "form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])";
10919  const formDisableSelector = "input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled";
10920  const formEnableSelector = "input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled";
10921  const fileInputSelector = "input[name][type=file]:not([disabled])";
10922  const linkDisableSelector = "a[data-disable-with], a[data-disable]";
10923  const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]";
10924  let nonce = null;
10925  const loadCSPNonce = () => {
10926    const metaTag = document.querySelector("meta[name=csp-nonce]");
10927    return nonce = metaTag && metaTag.content;
10928  };
10929  const cspNonce = () => nonce || loadCSPNonce();
10930  const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;
10931  const matches = function(element, selector) {
10932    if (selector.exclude) {
10933      return m.call(element, selector.selector) && !m.call(element, selector.exclude);
10934    } else {
10935      return m.call(element, selector);
10936    }
10937  };
10938  const EXPANDO = "_ujsData";
10939  const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined;
10940  const setData = function(element, key, value) {
10941    if (!element[EXPANDO]) {
10942      element[EXPANDO] = {};
10943    }
10944    return element[EXPANDO][key] = value;
10945  };
10946  const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector));
10947  const isContentEditable = function(element) {
10948    var isEditable = false;
10949    do {
10950      if (element.isContentEditable) {
10951        isEditable = true;
10952        break;
10953      }
10954      element = element.parentElement;
10955    } while (element);
10956    return isEditable;
10957  };
10958  const csrfToken = () => {
10959    const meta = document.querySelector("meta[name=csrf-token]");
10960    return meta && meta.content;
10961  };
10962  const csrfParam = () => {
10963    const meta = document.querySelector("meta[name=csrf-param]");
10964    return meta && meta.content;
10965  };
10966  const CSRFProtection = xhr => {
10967    const token = csrfToken();
10968    if (token) {
10969      return xhr.setRequestHeader("X-CSRF-Token", token);
10970    }
10971  };
10972  const refreshCSRFTokens = () => {
10973    const token = csrfToken();
10974    const param = csrfParam();
10975    if (token && param) {
10976      return $('form input[name="' + param + '"]').forEach((input => input.value = token));
10977    }
10978  };
10979  const AcceptHeaders = {
10980    "*": "*/*",
10981    text: "text/plain",
10982    html: "text/html",
10983    xml: "application/xml, text/xml",
10984    json: "application/json, text/javascript",
10985    script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
10986  };
10987  const ajax = options => {
10988    options = prepareOptions(options);
10989    var xhr = createXHR(options, (function() {
10990      const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type"));
10991      if (Math.floor(xhr.status / 100) === 2) {
10992        if (typeof options.success === "function") {
10993          options.success(response, xhr.statusText, xhr);
10994        }
10995      } else {
10996        if (typeof options.error === "function") {
10997          options.error(response, xhr.statusText, xhr);
10998        }
10999      }
11000      return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined;
11001    }));
11002    if (options.beforeSend && !options.beforeSend(xhr, options)) {
11003      return false;
11004    }
11005    if (xhr.readyState === XMLHttpRequest.OPENED) {
11006      return xhr.send(options.data);
11007    }
11008  };
11009  var prepareOptions = function(options) {
11010    options.url = options.url || location.href;
11011    options.type = options.type.toUpperCase();
11012    if (options.type === "GET" && options.data) {
11013      if (options.url.indexOf("?") < 0) {
11014        options.url += "?" + options.data;
11015      } else {
11016        options.url += "&" + options.data;
11017      }
11018    }
11019    if (!(options.dataType in AcceptHeaders)) {
11020      options.dataType = "*";
11021    }
11022    options.accept = AcceptHeaders[options.dataType];
11023    if (options.dataType !== "*") {
11024      options.accept += ", */*; q=0.01";
11025    }
11026    return options;
11027  };
11028  var createXHR = function(options, done) {
11029    const xhr = new XMLHttpRequest;
11030    xhr.open(options.type, options.url, true);
11031    xhr.setRequestHeader("Accept", options.accept);
11032    if (typeof options.data === "string") {
11033      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
11034    }
11035    if (!options.crossDomain) {
11036      xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
11037      CSRFProtection(xhr);
11038    }
11039    xhr.withCredentials = !!options.withCredentials;
11040    xhr.onreadystatechange = function() {
11041      if (xhr.readyState === XMLHttpRequest.DONE) {
11042        return done(xhr);
11043      }
11044    };
11045    return xhr;
11046  };
11047  var processResponse = function(response, type) {
11048    if (typeof response === "string" && typeof type === "string") {
11049      if (type.match(/\bjson\b/)) {
11050        try {
11051          response = JSON.parse(response);
11052        } catch (error) {}
11053      } else if (type.match(/\b(?:java|ecma)script\b/)) {
11054        const script = document.createElement("script");
11055        script.setAttribute("nonce", cspNonce());
11056        script.text = response;
11057        document.head.appendChild(script).parentNode.removeChild(script);
11058      } else if (type.match(/\b(xml|html|svg)\b/)) {
11059        const parser = new DOMParser;
11060        type = type.replace(/;.+/, "");
11061        try {
11062          response = parser.parseFromString(response, type);
11063        } catch (error1) {}
11064      }
11065    }
11066    return response;
11067  };
11068  const href = element => element.href;
11069  const isCrossDomain = function(url) {
11070    const originAnchor = document.createElement("a");
11071    originAnchor.href = location.href;
11072    const urlAnchor = document.createElement("a");
11073    try {
11074      urlAnchor.href = url;
11075      return !((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host || originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host);
11076    } catch (e) {
11077      return true;
11078    }
11079  };
11080  let preventDefault;
11081  let {CustomEvent: CustomEvent} = window;
11082  if (typeof CustomEvent !== "function") {
11083    CustomEvent = function(event, params) {
11084      const evt = document.createEvent("CustomEvent");
11085      evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
11086      return evt;
11087    };
11088    CustomEvent.prototype = window.Event.prototype;
11089    ({preventDefault: preventDefault} = CustomEvent.prototype);
11090    CustomEvent.prototype.preventDefault = function() {
11091      const result = preventDefault.call(this);
11092      if (this.cancelable && !this.defaultPrevented) {
11093        Object.defineProperty(this, "defaultPrevented", {
11094          get() {
11095            return true;
11096          }
11097        });
11098      }
11099      return result;
11100    };
11101  }
11102  const fire = (obj, name, data) => {
11103    const event = new CustomEvent(name, {
11104      bubbles: true,
11105      cancelable: true,
11106      detail: data
11107    });
11108    obj.dispatchEvent(event);
11109    return !event.defaultPrevented;
11110  };
11111  const stopEverything = e => {
11112    fire(e.target, "ujs:everythingStopped");
11113    e.preventDefault();
11114    e.stopPropagation();
11115    e.stopImmediatePropagation();
11116  };
11117  const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) {
11118    let {target: target} = e;
11119    while (!!(target instanceof Element) && !matches(target, selector)) {
11120      target = target.parentNode;
11121    }
11122    if (target instanceof Element && handler.call(target, e) === false) {
11123      e.preventDefault();
11124      e.stopPropagation();
11125    }
11126  }));
11127  const toArray = e => Array.prototype.slice.call(e);
11128  const serializeElement = (element, additionalParam) => {
11129    let inputs = [ element ];
11130    if (matches(element, "form")) {
11131      inputs = toArray(element.elements);
11132    }
11133    const params = [];
11134    inputs.forEach((function(input) {
11135      if (!input.name || input.disabled) {
11136        return;
11137      }
11138      if (matches(input, "fieldset[disabled] *")) {
11139        return;
11140      }
11141      if (matches(input, "select")) {
11142        toArray(input.options).forEach((function(option) {
11143          if (option.selected) {
11144            params.push({
11145              name: input.name,
11146              value: option.value
11147            });
11148          }
11149        }));
11150      } else if (input.checked || [ "radio", "checkbox", "submit" ].indexOf(input.type) === -1) {
11151        params.push({
11152          name: input.name,
11153          value: input.value
11154        });
11155      }
11156    }));
11157    if (additionalParam) {
11158      params.push(additionalParam);
11159    }
11160    return params.map((function(param) {
11161      if (param.name) {
11162        return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`;
11163      } else {
11164        return param;
11165      }
11166    })).join("&");
11167  };
11168  const formElements = (form, selector) => {
11169    if (matches(form, "form")) {
11170      return toArray(form.elements).filter((el => matches(el, selector)));
11171    } else {
11172      return toArray(form.querySelectorAll(selector));
11173    }
11174  };
11175  const handleConfirmWithRails = rails => function(e) {
11176    if (!allowAction(this, rails)) {
11177      stopEverything(e);
11178    }
11179  };
11180  const confirm = (message, element) => window.confirm(message);
11181  var allowAction = function(element, rails) {
11182    let callback;
11183    const message = element.getAttribute("data-confirm");
11184    if (!message) {
11185      return true;
11186    }
11187    let answer = false;
11188    if (fire(element, "confirm")) {
11189      try {
11190        answer = rails.confirm(message, element);
11191      } catch (error) {}
11192      callback = fire(element, "confirm:complete", [ answer ]);
11193    }
11194    return answer && callback;
11195  };
11196  const handleDisabledElement = function(e) {
11197    const element = this;
11198    if (element.disabled) {
11199      stopEverything(e);
11200    }
11201  };
11202  const enableElement = e => {
11203    let element;
11204    if (e instanceof Event) {
11205      if (isXhrRedirect(e)) {
11206        return;
11207      }
11208      element = e.target;
11209    } else {
11210      element = e;
11211    }
11212    if (isContentEditable(element)) {
11213      return;
11214    }
11215    if (matches(element, linkDisableSelector)) {
11216      return enableLinkElement(element);
11217    } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) {
11218      return enableFormElement(element);
11219    } else if (matches(element, formSubmitSelector)) {
11220      return enableFormElements(element);
11221    }
11222  };
11223  const disableElement = e => {
11224    const element = e instanceof Event ? e.target : e;
11225    if (isContentEditable(element)) {
11226      return;
11227    }
11228    if (matches(element, linkDisableSelector)) {
11229      return disableLinkElement(element);
11230    } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) {
11231      return disableFormElement(element);
11232    } else if (matches(element, formSubmitSelector)) {
11233      return disableFormElements(element);
11234    }
11235  };
11236  var disableLinkElement = function(element) {
11237    if (getData(element, "ujs:disabled")) {
11238      return;
11239    }
11240    const replacement = element.getAttribute("data-disable-with");
11241    if (replacement != null) {
11242      setData(element, "ujs:enable-with", element.innerHTML);
11243      element.innerHTML = replacement;
11244    }
11245    element.addEventListener("click", stopEverything);
11246    return setData(element, "ujs:disabled", true);
11247  };
11248  var enableLinkElement = function(element) {
11249    const originalText = getData(element, "ujs:enable-with");
11250    if (originalText != null) {
11251      element.innerHTML = originalText;
11252      setData(element, "ujs:enable-with", null);
11253    }
11254    element.removeEventListener("click", stopEverything);
11255    return setData(element, "ujs:disabled", null);
11256  };
11257  var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement);
11258  var disableFormElement = function(element) {
11259    if (getData(element, "ujs:disabled")) {
11260      return;
11261    }
11262    const replacement = element.getAttribute("data-disable-with");
11263    if (replacement != null) {
11264      if (matches(element, "button")) {
11265        setData(element, "ujs:enable-with", element.innerHTML);
11266        element.innerHTML = replacement;
11267      } else {
11268        setData(element, "ujs:enable-with", element.value);
11269        element.value = replacement;
11270      }
11271    }
11272    element.disabled = true;
11273    return setData(element, "ujs:disabled", true);
11274  };
11275  var enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element)));
11276  var enableFormElement = function(element) {
11277    const originalText = getData(element, "ujs:enable-with");
11278    if (originalText != null) {
11279      if (matches(element, "button")) {
11280        element.innerHTML = originalText;
11281      } else {
11282        element.value = originalText;
11283      }
11284      setData(element, "ujs:enable-with", null);
11285    }
11286    element.disabled = false;
11287    return setData(element, "ujs:disabled", null);
11288  };
11289  var isXhrRedirect = function(event) {
11290    const xhr = event.detail ? event.detail[0] : undefined;
11291    return xhr && xhr.getResponseHeader("X-Xhr-Redirect");
11292  };
11293  const handleMethodWithRails = rails => function(e) {
11294    const link = this;
11295    const method = link.getAttribute("data-method");
11296    if (!method) {
11297      return;
11298    }
11299    if (isContentEditable(this)) {
11300      return;
11301    }
11302    const href = rails.href(link);
11303    const csrfToken$1 = csrfToken();
11304    const csrfParam$1 = csrfParam();
11305    const form = document.createElement("form");
11306    let formContent = `<input name='_method' value='${method}' type='hidden' />`;
11307    if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) {
11308      formContent += `<input name='${csrfParam$1}' value='${csrfToken$1}' type='hidden' />`;
11309    }
11310    formContent += '<input type="submit" />';
11311    form.method = "post";
11312    form.action = href;
11313    form.target = link.target;
11314    form.innerHTML = formContent;
11315    form.style.display = "none";
11316    document.body.appendChild(form);
11317    form.querySelector('[type="submit"]').click();
11318    stopEverything(e);
11319  };
11320  const isRemote = function(element) {
11321    const value = element.getAttribute("data-remote");
11322    return value != null && value !== "false";
11323  };
11324  const handleRemoteWithRails = rails => function(e) {
11325    let data, method, url;
11326    const element = this;
11327    if (!isRemote(element)) {
11328      return true;
11329    }
11330    if (!fire(element, "ajax:before")) {
11331      fire(element, "ajax:stopped");
11332      return false;
11333    }
11334    if (isContentEditable(element)) {
11335      fire(element, "ajax:stopped");
11336      return false;
11337    }
11338    const withCredentials = element.getAttribute("data-with-credentials");
11339    const dataType = element.getAttribute("data-type") || "script";
11340    if (matches(element, formSubmitSelector)) {
11341      const button = getData(element, "ujs:submit-button");
11342      method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get";
11343      url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href;
11344      if (method.toUpperCase() === "GET") {
11345        url = url.replace(/\?.*$/, "");
11346      }
11347      if (element.enctype === "multipart/form-data") {
11348        data = new FormData(element);
11349        if (button != null) {
11350          data.append(button.name, button.value);
11351        }
11352      } else {
11353        data = serializeElement(element, button);
11354      }
11355      setData(element, "ujs:submit-button", null);
11356      setData(element, "ujs:submit-button-formmethod", null);
11357      setData(element, "ujs:submit-button-formaction", null);
11358    } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) {
11359      method = element.getAttribute("data-method");
11360      url = element.getAttribute("data-url");
11361      data = serializeElement(element, element.getAttribute("data-params"));
11362    } else {
11363      method = element.getAttribute("data-method");
11364      url = rails.href(element);
11365      data = element.getAttribute("data-params");
11366    }
11367    ajax({
11368      type: method || "GET",
11369      url: url,
11370      data: data,
11371      dataType: dataType,
11372      beforeSend(xhr, options) {
11373        if (fire(element, "ajax:beforeSend", [ xhr, options ])) {
11374          return fire(element, "ajax:send", [ xhr ]);
11375        } else {
11376          fire(element, "ajax:stopped");
11377          return false;
11378        }
11379      },
11380      success(...args) {
11381        return fire(element, "ajax:success", args);
11382      },
11383      error(...args) {
11384        return fire(element, "ajax:error", args);
11385      },
11386      complete(...args) {
11387        return fire(element, "ajax:complete", args);
11388      },
11389      crossDomain: isCrossDomain(url),
11390      withCredentials: withCredentials != null && withCredentials !== "false"
11391    });
11392    stopEverything(e);
11393  };
11394  const formSubmitButtonClick = function(e) {
11395    const button = this;
11396    const {form: form} = button;
11397    if (!form) {
11398      return;
11399    }
11400    if (button.name) {
11401      setData(form, "ujs:submit-button", {
11402        name: button.name,
11403        value: button.value
11404      });
11405    }
11406    setData(form, "ujs:formnovalidate-button", button.formNoValidate);
11407    setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction"));
11408    return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod"));
11409  };
11410  const preventInsignificantClick = function(e) {
11411    const link = this;
11412    const method = (link.getAttribute("data-method") || "GET").toUpperCase();
11413    const data = link.getAttribute("data-params");
11414    const metaClick = e.metaKey || e.ctrlKey;
11415    const insignificantMetaClick = metaClick && method === "GET" && !data;
11416    const nonPrimaryMouseClick = e.button != null && e.button !== 0;
11417    if (nonPrimaryMouseClick || insignificantMetaClick) {
11418      e.stopImmediatePropagation();
11419    }
11420  };
11421  const Rails = {
11422    $: $,
11423    ajax: ajax,
11424    buttonClickSelector: buttonClickSelector,
11425    buttonDisableSelector: buttonDisableSelector,
11426    confirm: confirm,
11427    cspNonce: cspNonce,
11428    csrfToken: csrfToken,
11429    csrfParam: csrfParam,
11430    CSRFProtection: CSRFProtection,
11431    delegate: delegate,
11432    disableElement: disableElement,
11433    enableElement: enableElement,
11434    fileInputSelector: fileInputSelector,
11435    fire: fire,
11436    formElements: formElements,
11437    formEnableSelector: formEnableSelector,
11438    formDisableSelector: formDisableSelector,
11439    formInputClickSelector: formInputClickSelector,
11440    formSubmitButtonClick: formSubmitButtonClick,
11441    formSubmitSelector: formSubmitSelector,
11442    getData: getData,
11443    handleDisabledElement: handleDisabledElement,
11444    href: href,
11445    inputChangeSelector: inputChangeSelector,
11446    isCrossDomain: isCrossDomain,
11447    linkClickSelector: linkClickSelector,
11448    linkDisableSelector: linkDisableSelector,
11449    loadCSPNonce: loadCSPNonce,
11450    matches: matches,
11451    preventInsignificantClick: preventInsignificantClick,
11452    refreshCSRFTokens: refreshCSRFTokens,
11453    serializeElement: serializeElement,
11454    setData: setData,
11455    stopEverything: stopEverything
11456  };
11457  const handleConfirm = handleConfirmWithRails(Rails);
11458  Rails.handleConfirm = handleConfirm;
11459  const handleMethod = handleMethodWithRails(Rails);
11460  Rails.handleMethod = handleMethod;
11461  const handleRemote = handleRemoteWithRails(Rails);
11462  Rails.handleRemote = handleRemote;
11463  const start = function() {
11464    if (window._rails_loaded) {
11465      throw new Error("rails-ujs has already been loaded!");
11466    }
11467    window.addEventListener("pageshow", (function() {
11468      $(formEnableSelector).forEach((function(el) {
11469        if (getData(el, "ujs:disabled")) {
11470          enableElement(el);
11471        }
11472      }));
11473      $(linkDisableSelector).forEach((function(el) {
11474        if (getData(el, "ujs:disabled")) {
11475          enableElement(el);
11476        }
11477      }));
11478    }));
11479    delegate(document, linkDisableSelector, "ajax:complete", enableElement);
11480    delegate(document, linkDisableSelector, "ajax:stopped", enableElement);
11481    delegate(document, buttonDisableSelector, "ajax:complete", enableElement);
11482    delegate(document, buttonDisableSelector, "ajax:stopped", enableElement);
11483    delegate(document, linkClickSelector, "click", preventInsignificantClick);
11484    delegate(document, linkClickSelector, "click", handleDisabledElement);
11485    delegate(document, linkClickSelector, "click", handleConfirm);
11486    delegate(document, linkClickSelector, "click", disableElement);
11487    delegate(document, linkClickSelector, "click", handleRemote);
11488    delegate(document, linkClickSelector, "click", handleMethod);
11489    delegate(document, buttonClickSelector, "click", preventInsignificantClick);
11490    delegate(document, buttonClickSelector, "click", handleDisabledElement);
11491    delegate(document, buttonClickSelector, "click", handleConfirm);
11492    delegate(document, buttonClickSelector, "click", disableElement);
11493    delegate(document, buttonClickSelector, "click", handleRemote);
11494    delegate(document, inputChangeSelector, "change", handleDisabledElement);
11495    delegate(document, inputChangeSelector, "change", handleConfirm);
11496    delegate(document, inputChangeSelector, "change", handleRemote);
11497    delegate(document, formSubmitSelector, "submit", handleDisabledElement);
11498    delegate(document, formSubmitSelector, "submit", handleConfirm);
11499    delegate(document, formSubmitSelector, "submit", handleRemote);
11500    delegate(document, formSubmitSelector, "submit", (e => setTimeout((() => disableElement(e)), 13)));
11501    delegate(document, formSubmitSelector, "ajax:send", disableElement);
11502    delegate(document, formSubmitSelector, "ajax:complete", enableElement);
11503    delegate(document, formInputClickSelector, "click", preventInsignificantClick);
11504    delegate(document, formInputClickSelector, "click", handleDisabledElement);
11505    delegate(document, formInputClickSelector, "click", handleConfirm);
11506    delegate(document, formInputClickSelector, "click", formSubmitButtonClick);
11507    document.addEventListener("DOMContentLoaded", refreshCSRFTokens);
11508    document.addEventListener("DOMContentLoaded", loadCSPNonce);
11509    return window._rails_loaded = true;
11510  };
11511  Rails.start = start;
11512  if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) {
11513    if (jQuery.rails) {
11514      throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only.");
11515    }
11516    jQuery.rails = Rails;
11517    jQuery.ajaxPrefilter((function(options, originalOptions, xhr) {
11518      if (!options.crossDomain) {
11519        return CSRFProtection(xhr);
11520      }
11521    }));
11522  }
11523  if (typeof exports !== "object" && typeof module === "undefined") {
11524    window.Rails = Rails;
11525    if (fire(document, "rails:attachBindings")) {
11526      start();
11527    }
11528  }
11529  return Rails;
11530}));
11531}
11532
11533;
11534/**
11535 * Modal Utilities - Reusable functions for consistent modal behavior across the site
11536 *
11537 * Standard behaviors:
11538 * - Safe overlay close (prevents accidental closes when dragging)
11539 * - ESC key closes modal
11540 * - Body scroll locking
11541 * - Focus management
11542 * - Accessibility support
11543 */
11544
11545
11546window.ModalUtilities = {
11547  /**
11548   * Lock body scroll when modal is open
11549   * Prevents page scrolling behind the modal
11550   *
11551   * @returns {Function} Unlock function to restore scrolling
11552   *
11553   * @example
11554   * const unlock = ModalUtilities.lockBodyScroll();
11555   * // Later, when modal closes:
11556   * unlock();
11557   */
11558  lockBodyScroll: function() {
11559    const originalOverflow = document.body.style.overflow;
11560    document.body.style.overflow = 'hidden';
11561
11562    return function unlock() {
11563      document.body.style.overflow = originalOverflow;
11564    };
11565  },
11566
11567  /**
11568   * Set focus to the first focusable element in a container
11569   * Improves accessibility and keyboard navigation
11570   *
11571   * @param {HTMLElement} container - The container to search for focusable elements
11572   * @returns {boolean} True if focus was set, false otherwise
11573   *
11574   * @example
11575   * ModalUtilities.setInitialFocus(document.querySelector('.modal-content'));
11576   */
11577  setInitialFocus: function(container) {
11578    if (!container) return false;
11579
11580    const focusableSelectors = [
11581      'button:not([disabled])',
11582      '[href]',
11583      'input:not([disabled])',
11584      'select:not([disabled])',
11585      'textarea:not([disabled])',
11586      '[tabindex]:not([tabindex="-1"])'
11587    ].join(', ');
11588
11589    const focusableElements = container.querySelectorAll(focusableSelectors);
11590    if (focusableElements.length > 0) {
11591      focusableElements[0].focus();
11592      return true;
11593    }
11594    return false;
11595  },
11596
11597  /**
11598   * Trap focus within a modal (prevent tabbing out)
11599   * Ensures keyboard users stay within the modal
11600   *
11601   * @param {HTMLElement} container - The modal container
11602   * @returns {Function} Cleanup function to remove focus trap
11603   *
11604   * @example
11605   * const cleanup = ModalUtilities.trapFocus(modalElement);
11606   * // Later, when modal closes:
11607   * cleanup();
11608   */
11609  trapFocus: function(container) {
11610    if (!container) return () => {};
11611
11612    const focusableSelectors = [
11613      'button:not([disabled])',
11614      '[href]',
11615      'input:not([disabled])',
11616      'select:not([disabled])',
11617      'textarea:not([disabled])',
11618      '[tabindex]:not([tabindex="-1"])'
11619    ].join(', ');
11620
11621    const handleKeyDown = (event) => {
11622      if (event.key !== 'Tab' && event.keyCode !== 9) return;
11623
11624      const focusableElements = container.querySelectorAll(focusableSelectors);
11625      const firstElement = focusableElements[0];
11626      const lastElement = focusableElements[focusableElements.length - 1];
11627
11628      if (event.shiftKey && document.activeElement === firstElement) {
11629        event.preventDefault();
11630        lastElement.focus();
11631      } else if (!event.shiftKey && document.activeElement === lastElement) {
11632        event.preventDefault();
11633        firstElement.focus();
11634      }
11635    };
11636
11637    document.addEventListener('keydown', handleKeyDown);
11638
11639    return function cleanup() {
11640      document.removeEventListener('keydown', handleKeyDown);
11641    };
11642  },
11643
11644  /**
11645   * Setup safe overlay close behavior for a modal
11646   * Prevents accidental closes when user starts clicking inside modal and drags out
11647   *
11648   * @param {HTMLElement} overlayElement - The overlay element (usually dark background)
11649   * @param {Function} closeCallback - Function to call when modal should close
11650   * @param {Object} options - Optional configuration
11651   * @param {boolean} options.escapeKey - Enable ESC key to close (default: true)
11652   * @returns {Function} Cleanup function to remove event listeners
11653   *
11654   * @example
11655   * const cleanup = ModalUtilities.setupSafeOverlayClose(
11656   *   document.querySelector('.modal-overlay'),
11657   *   () => closeMyModal()
11658   * );
11659   * // Later, when modal is destroyed:
11660   * cleanup();
11661   */
11662  setupSafeOverlayClose: function(overlayElement, closeCallback, options = {}) {
11663    if (!overlayElement || typeof closeCallback !== 'function') {
11664      console.error('ModalUtilities.setupSafeOverlayClose: Invalid arguments');
11665      return () => {};
11666    }
11667
11668    const enableEscapeKey = options.escapeKey !== false;
11669    let mouseDownOnOverlay = false;
11670    let mouseUpOnOverlay = false;
11671
11672    // Track where mousedown started
11673    const handleMouseDown = (event) => {
11674      mouseDownOnOverlay = (event.target === overlayElement);
11675    };
11676
11677    // Track where mouseup happened
11678    const handleMouseUp = (event) => {
11679      mouseUpOnOverlay = (event.target === overlayElement);
11680    };
11681
11682    // Only close if BOTH mousedown AND mouseup happened on overlay
11683    // This prevents accidental closes when dragging in/out of modal
11684    const handleClick = (event) => {
11685      if (event.target === overlayElement && mouseDownOnOverlay && mouseUpOnOverlay) {
11686        closeCallback();
11687      }
11688      // Reset flags
11689      mouseDownOnOverlay = false;
11690      mouseUpOnOverlay = false;
11691    };
11692
11693    // ESC key handler
11694    const handleEscapeKey = (event) => {
11695      if (event.key === 'Escape' || event.keyCode === 27) {
11696        closeCallback();
11697      }
11698    };
11699
11700    // Add event listeners
11701    overlayElement.addEventListener('mousedown', handleMouseDown);
11702    overlayElement.addEventListener('mouseup', handleMouseUp);
11703    overlayElement.addEventListener('click', handleClick);
11704    if (enableEscapeKey) {
11705      document.addEventListener('keydown', handleEscapeKey);
11706    }
11707
11708    // Return cleanup function
11709    return function cleanup() {
11710      overlayElement.removeEventListener('mousedown', handleMouseDown);
11711      overlayElement.removeEventListener('mouseup', handleMouseUp);
11712      overlayElement.removeEventListener('click', handleClick);
11713      if (enableEscapeKey) {
11714        document.removeEventListener('keydown', handleEscapeKey);
11715      }
11716    };
11717  },
11718
11719  /**
11720   * Setup modal with all standard behaviors
11721   * Convenience method that combines multiple common modal behaviors
11722   *
11723   * @param {Object} config - Configuration object
11724   * @param {HTMLElement} config.overlay - The overlay element
11725   * @param {HTMLElement} config.modal - The modal content element
11726   * @param {HTMLElement} config.closeButton - Optional close button element
11727   * @param {Function} config.onClose - Callback when modal closes
11728   * @param {boolean} config.escapeKey - Enable ESC key (default: true)
11729   * @param {boolean} config.lockScroll - Lock body scroll (default: true)
11730   * @param {boolean} config.trapFocus - Trap focus in modal (default: true)
11731   * @param {boolean} config.setInitialFocus - Set focus to first element (default: true)
11732   * @returns {Function} Cleanup function to remove all event listeners and restore state
11733   *
11734   * @example
11735   * const cleanup = ModalUtilities.setupModal({
11736   *   overlay: document.querySelector('.modal-overlay'),
11737   *   modal: document.querySelector('.modal-content'),
11738   *   closeButton: document.querySelector('.modal-close'),
11739   *   onClose: () => { myModal.hide(); }
11740   * });
11741   * // Later, when modal closes:
11742   * cleanup();
11743   */
11744  setupModal: function(config) {
11745    const cleanups = [];
11746
11747    // Lock body scroll
11748    if (config.lockScroll !== false) {
11749      const unlockScroll = this.lockBodyScroll();
11750      cleanups.push(unlockScroll);
11751    }
11752
11753    // Setup safe overlay close
11754    if (config.overlay && config.onClose) {
11755      const overlayCleanup = this.setupSafeOverlayClose(
11756        config.overlay,
11757        config.onClose,
11758        { escapeKey: config.escapeKey }
11759      );
11760      cleanups.push(overlayCleanup);
11761    }
11762
11763    // Setup close button
11764    if (config.closeButton && config.onClose) {
11765      const handleCloseClick = () => config.onClose();
11766      config.closeButton.addEventListener('click', handleCloseClick);
11767      cleanups.push(() => {
11768        config.closeButton.removeEventListener('click', handleCloseClick);
11769      });
11770    }
11771
11772    // Trap focus within modal
11773    if (config.modal && config.trapFocus !== false) {
11774      const trapCleanup = this.trapFocus(config.modal);
11775      cleanups.push(trapCleanup);
11776    }
11777
11778    // Set initial focus
11779    if (config.modal && config.setInitialFocus !== false) {
11780      // Use setTimeout to ensure DOM is ready
11781      setTimeout(() => {
11782        this.setInitialFocus(config.modal);
11783      }, 100);
11784    }
11785
11786    // Return combined cleanup function
11787    return function cleanup() {
11788      cleanups.forEach(fn => fn());
11789    };
11790  },
11791
11792  /**
11793   * Show a friendly confirm modal instead of window.confirm
11794   * Uses the generic-confirm-modal in the layout
11795   *
11796   * @param {string} message - Message to display
11797   * @param {string} title - Optional title (default: "Confirm")
11798   * @param {string} confirmText - Optional confirm button text (default: "Confirm")
11799   * @param {string} cancelText - Optional cancel button text (default: "Cancel")
11800   * @returns {Promise<boolean>} - Resolves to true if confirmed, false if cancelled
11801   *
11802   * @example
11803   * const confirmed = await ModalUtilities.showConfirmModal(
11804   *   'Are you sure you want to delete this?',
11805   *   'Confirm Delete',
11806   *   'Delete',
11807   *   'Cancel'
11808   * );
11809   * if (confirmed) {
11810   *   // User clicked confirm
11811   * }
11812   */
11813  showConfirmModal: function(message, title = 'Confirm', confirmText = 'Confirm', cancelText = 'Cancel') {
11814    return new Promise((resolve) => {
11815      const modal = document.getElementById('generic-confirm-modal');
11816      const titleEl = document.getElementById('generic-confirm-title');
11817      const messageEl = document.getElementById('generic-confirm-message');
11818      const confirmBtn = document.getElementById('generic-confirm-ok-btn');
11819      const cancelBtn = document.getElementById('generic-confirm-cancel-btn');
11820      const icon = document.getElementById('generic-confirm-icon');
11821
11822      if (!modal || !titleEl || !messageEl || !confirmBtn || !cancelBtn) {
11823        console.error('ModalUtilities.showConfirmModal: Required modal elements not found');
11824        resolve(false);
11825        return;
11826      }
11827
11828      // Set content
11829      titleEl.textContent = title;
11830      messageEl.textContent = message;
11831      confirmBtn.textContent = confirmText;
11832      cancelBtn.textContent = cancelText;
11833
11834      // Reset icon color to warning (amber)
11835      if (icon) {
11836        icon.style.background = '#FEF3C7';
11837        icon.style.color = '#92400E';
11838      }
11839
11840      // Show modal
11841      modal.style.display = 'flex';
11842
11843      // One-time event handlers
11844      function handleConfirm() {
11845        modal.style.display = 'none';
11846        cleanup();
11847        resolve(true);
11848      }
11849
11850      function handleCancel() {
11851        modal.style.display = 'none';
11852        cleanup();
11853        resolve(false);
11854      }
11855
11856      function handleOverlayClick(e) {
11857        if (e.target === modal) {
11858          handleCancel();
11859        }
11860      }
11861
11862      function cleanup() {
11863        confirmBtn.removeEventListener('click', handleConfirm);
11864        cancelBtn.removeEventListener('click', handleCancel);
11865        modal.removeEventListener('click', handleOverlayClick);
11866      }
11867
11868      confirmBtn.addEventListener('click', handleConfirm);
11869      cancelBtn.addEventListener('click', handleCancel);
11870      modal.addEventListener('click', handleOverlayClick);
11871    });
11872  },
11873
11874  /**
11875   * Show a friendly alert modal instead of window.alert
11876   * Uses the generic-alert-modal in the layout
11877   *
11878   * @param {string} message - Message to display
11879   * @param {string} title - Optional title (default: "Notice")
11880   * @param {string} type - Optional type: 'info', 'success', 'error' (default: 'info')
11881   * @returns {Promise<void>} - Resolves when modal is closed
11882   *
11883   * @example
11884   * await ModalUtilities.showAlertModal(
11885   *   'Your changes have been saved.',
11886   *   'Success',
11887   *   'success'
11888   * );
11889   */
11890  showAlertModal: function(message, title = 'Notice', type = 'info') {
11891    return new Promise((resolve) => {
11892      const modal = document.getElementById('generic-alert-modal');
11893      const titleEl = document.getElementById('generic-alert-title');
11894      const messageEl = document.getElementById('generic-alert-message');
11895      const okBtn = document.getElementById('generic-alert-ok-btn');
11896      const icon = document.getElementById('generic-alert-icon');
11897
11898      if (!modal || !titleEl || !messageEl || !okBtn) {
11899        console.error('ModalUtilities.showAlertModal: Required modal elements not found');
11900        resolve();
11901        return;
11902      }
11903
11904      // Set content
11905      titleEl.textContent = title;
11906      messageEl.textContent = message;
11907
11908      // Set icon color based on type
11909      if (icon) {
11910        switch(type) {
11911          case 'success':
11912            icon.style.background = '#D1FAE5';
11913            icon.style.color = '#065F46';
11914            break;
11915          case 'error':
11916            icon.style.background = '#FEE2E2';
11917            icon.style.color = '#991B1B';
11918            break;
11919          default: // info
11920            icon.style.background = '#DBEAFE';
11921            icon.style.color = '#1E40AF';
11922        }
11923      }
11924
11925      // Show modal
11926      modal.style.display = 'flex';
11927
11928      // One-time event handlers
11929      function handleOk() {
11930        modal.style.display = 'none';
11931        cleanup();
11932        resolve();
11933      }
11934
11935      function handleOverlayClick(e) {
11936        if (e.target === modal) {
11937          handleOk();
11938        }
11939      }
11940
11941      function cleanup() {
11942        okBtn.removeEventListener('click', handleOk);
11943        modal.removeEventListener('click', handleOverlayClick);
11944      }
11945
11946      okBtn.addEventListener('click', handleOk);
11947      modal.addEventListener('click', handleOverlayClick);
11948    });
11949  },
11950
11951  /**
11952   * Show a friendly prompt modal instead of window.prompt
11953   * Uses the generic-prompt-modal in the layout
11954   *
11955   * @param {string} message - Message to display
11956   * @param {string} title - Optional title (default: "Enter Value")
11957   * @param {string} placeholder - Optional placeholder text
11958   * @param {string} defaultValue - Optional default value
11959   * @returns {Promise<string|null>} - Resolves to the entered value, or null if cancelled
11960   *
11961   * @example
11962   * const url = await ModalUtilities.showPromptModal(
11963   *   'Enter the URL:',
11964   *   'Add Link',
11965   *   'https://',
11966   *   ''
11967   * );
11968   * if (url) {
11969   *   // User entered a value
11970   * }
11971   */
11972  showPromptModal: function(message, title = 'Enter Value', placeholder = '', defaultValue = '') {
11973    return new Promise((resolve) => {
11974      const modal = document.getElementById('generic-prompt-modal');
11975      const titleEl = document.getElementById('generic-prompt-title');
11976      const messageEl = document.getElementById('generic-prompt-message');
11977      const inputEl = document.getElementById('generic-prompt-input');
11978      const okBtn = document.getElementById('generic-prompt-ok-btn');
11979      const cancelBtn = document.getElementById('generic-prompt-cancel-btn');
11980
11981      if (!modal || !titleEl || !messageEl || !inputEl || !okBtn || !cancelBtn) {
11982        console.error('ModalUtilities.showPromptModal: Required modal elements not found');
11983        resolve(null);
11984        return;
11985      }
11986
11987      // Set content
11988      titleEl.textContent = title;
11989      messageEl.textContent = message;
11990      inputEl.placeholder = placeholder;
11991      inputEl.value = defaultValue;
11992
11993      // Show modal
11994      modal.style.display = 'flex';
11995
11996      // Focus input after modal is visible
11997      setTimeout(() => inputEl.focus(), 100);
11998
11999      // One-time event handlers
12000      function handleOk() {
12001        const value = inputEl.value.trim();
12002        modal.style.display = 'none';
12003        cleanup();
12004        resolve(value || null);
12005      }
12006
12007      function handleCancel() {
12008        modal.style.display = 'none';
12009        cleanup();
12010        resolve(null);
12011      }
12012
12013      function handleKeyPress(e) {
12014        if (e.key === 'Enter') {
12015          handleOk();
12016        } else if (e.key === 'Escape') {
12017          handleCancel();
12018        }
12019      }
12020
12021      function handleOverlayClick(e) {
12022        if (e.target === modal) {
12023          handleCancel();
12024        }
12025      }
12026
12027      function cleanup() {
12028        okBtn.removeEventListener('click', handleOk);
12029        cancelBtn.removeEventListener('click', handleCancel);
12030        inputEl.removeEventListener('keypress', handleKeyPress);
12031        modal.removeEventListener('click', handleOverlayClick);
12032      }
12033
12034      okBtn.addEventListener('click', handleOk);
12035      cancelBtn.addEventListener('click', handleCancel);
12036      inputEl.addEventListener('keypress', handleKeyPress);
12037      modal.addEventListener('click', handleOverlayClick);
12038    });
12039  }
12040};
12041// RmModal — blessed modal chassis JS (SPEC.md D1). Folds in modal_utilities.js's
12042// focus-trap / scroll-lock / safe-overlay-close so this component has no
12043// runtime load-order dependency on that global (isolation: nothing wires this
12044// up yet).
12045//
12046// Markup contract:
12047//   <div class="rm-modal__overlay" data-rm-modal-name="example">
12048//     <div class="rm-modal__dialog rm-modal__dialog--md" role="dialog" aria-modal="true">...
12049//   <button data-rm-modal="example">Open</button>                  (preloaded)
12050//   <button data-rm-modal="example" data-rm-modal-url="/x">Open</button> (AJAX; SPEC rule 7)
12051//   <button data-rm-modal-close>...</button>                        (inside any dialog)
12052//
12053// window.RmModal = { open(idOrEl), close([idOrEl]), bindTriggers() }
12054// - open/close accept a name string, a trigger element, or an overlay element.
12055// - close() with no arg closes the topmost modal (stack-aware: ESC and
12056//   backdrop click only ever affect the top of the stack).
12057(function () {
12058  'use strict';
12059
12060  // Sprockets can evaluate this file more than once on a page (several bundles
12061  // require base, and layouts may load two bundles — e.g. a page bundle + teams).
12062  // Two live copies means two document-level click handlers, which double-open
12063  // and double-inject AJAX modals. First copy wins; the rest no-op.
12064  if (window.RmModal) return;
12065
12066  var SEL = {
12067    overlay: '.rm-modal__overlay',
12068    dialog: '.rm-modal__dialog'
12069  };
12070  var OPEN_CLASS = 'rm-modal__overlay--open';
12071  var FOCUSABLE = [
12072    'button:not([disabled])',
12073    '[href]',
12074    'input:not([disabled])',
12075    'select:not([disabled])',
12076    'textarea:not([disabled])',
12077    '[tabindex]:not([tabindex="-1"])'
12078  ].join(', ');
12079
12080  var stack = [];                  // open overlays, bottom to top
12081  var returnFocus = new WeakMap(); // overlay -> element to refocus on close
12082  var trapCleanup = null;          // teardown for the current top overlay's trap
12083  var triggersBound = false;       // one-time guard for the document-level bindings
12084
12085  // --- scroll lock, ref-counted by stack depth --------------------------------
12086
12087  // Locks <html> AND <body>. Body alone does NOTHING on this site:
12088  // responsive.css.scss:269 sets `html { overflow-y: auto }`, and once <html> carries a
12089  // non-`visible` overflow the browser stops propagating body's overflow to the viewport.
12090  // document.scrollingElement is HTML, so the element being locked was never the scroller.
12091  // Measured with the wizard open at 390x844 BEFORE this change: body's computed overflow
12092  // was `hidden` and a real wheel gesture over the backdrop still scrolled the page.
12093  // Caught by the round-3 review — my own check had confirmed the PROPERTY was set and
12094  // never asked whether any scrolling was actually prevented.
12095  //
12096  // TEST THIS WITH A WHEEL OR TOUCH GESTURE, NOT window.scrollTo(). overflow:hidden blocks
12097  // USER scrolling; programmatic scrolling still works by design, so scrollTo() reports a
12098  // working lock as broken. An earlier version of this comment cited a scrollTo measurement
12099  // as its evidence, which would have handed the next reader the same dead end.
12100  function lockScroll() {
12101    if (stack.length !== 1) return; // a deeper modal: already locked
12102    document.body.dataset.rmModalPrevOverflow = document.body.style.overflow || '';
12103    document.documentElement.dataset.rmModalPrevOverflow = document.documentElement.style.overflow || '';
12104    document.body.style.overflow = 'hidden';
12105    document.documentElement.style.overflow = 'hidden';
12106  }
12107
12108  function unlockScrollIfEmpty() {
12109    if (stack.length) return;
12110    document.body.style.overflow = document.body.dataset.rmModalPrevOverflow || '';
12111    document.documentElement.style.overflow = document.documentElement.dataset.rmModalPrevOverflow || '';
12112    delete document.body.dataset.rmModalPrevOverflow;
12113    delete document.documentElement.dataset.rmModalPrevOverflow;
12114  }
12115
12116  // --- focus trap: one active trap, re-targeted as the stack changes ----------
12117
12118  // A control that is display:none / visibility:hidden / inside a collapsed step is still
12119  // returned by querySelectorAll(FOCUSABLE) but Tab never reaches it. That made
12120  // focusable[length-1] an element the user could not focus, so the wrap condition
12121  // (activeElement === last) never fired and the trap leaked — measured at 9-10 of 12 Tab
12122  // presses escaping to the page behind, on BOTH rebuilt pages, because their modals are
12123  // multi-step wizards whose later steps are in the DOM from the start.
12124  // offsetParent is null for anything display:none or in a hidden subtree; the rect check
12125  // catches visibility:hidden and zero-size controls.
12126  //
12127  // Shared with the focus-trap wait in activateTrapFor: both mean "on screen right now".
12128  function isVisible(el) {
12129    if (el.offsetParent === null && getComputedStyle(el).position !== 'fixed') return false;
12130    if (getComputedStyle(el).visibility === 'hidden') return false;
12131    var r = el.getBoundingClientRect();
12132    return r.width > 0 && r.height > 0;
12133  }
12134
12135  function visibleFocusable(container) {
12136    return Array.prototype.filter.call(container.querySelectorAll(FOCUSABLE), isVisible);
12137  }
12138
12139  function trapFocus(container) {
12140    function onKeydown(e) {
12141      if (e.key !== 'Tab') return;
12142      var focusable = visibleFocusable(container);
12143      if (!focusable.length) return;
12144      var first = focusable[0];
12145      var last = focusable[focusable.length - 1];
12146      if (e.shiftKey && document.activeElement === first) {
12147        e.preventDefault();
12148        last.focus();
12149      } else if (!e.shiftKey && document.activeElement === last) {
12150        e.preventDefault();
12151        first.focus();
12152      }
12153    }
12154    document.addEventListener('keydown', onKeydown);
12155    return function cleanup() {
12156      document.removeEventListener('keydown', onKeydown);
12157    };
12158  }
12159
12160  function activateTrapFor(overlay) {
12161    if (trapCleanup) trapCleanup();
12162    var dialog = overlay.querySelector(SEL.dialog) || overlay;
12163    trapCleanup = trapFocus(dialog);
12164    // Wait for the opening transition to actually make the dialog focusable — do not
12165    // GUESS at how long it takes.
12166    //
12167    // This was `setTimeout(..., 50)`. The overlay opens from `visibility: hidden`, which
12168    // isVisible() rejects, so when 50ms landed mid-transition the focusable list came back
12169    // empty AND the `dialog.focus()` fallback was a no-op on a still-hidden element: focus
12170    // stayed wherever it was, which for a declarative trigger is the button on the page
12171    // BEHIND the backdrop. Measured on /races/289812 at 1280x900, opening the admin invite
12172    // modal 8 times through its real trigger: focus escaped 4 times out of 8. Through
12173    // RmModal.open() it never escaped, which is why every earlier check of this passed —
12174    // they all called the API.
12175    //
12176    // Terminates on CLOSE rather than on a frame budget. A fixed cap only reinstates the
12177    // bug on any page slow enough to blow it, and a backgrounded tab throttles rAF to
12178    // roughly one frame per 500ms, where 40 frames is 20 seconds rather than 2/3 of one.
12179    // Gating on "still open" is both cheaper and correct: the loop cannot outlive the
12180    // modal, and a modal nobody can see is a modal with nothing to focus.
12181    //
12182    // Two conditions, because neither alone is right, and each was measured wrong here:
12183    //
12184    //   Gate on visible FOCUSABLES alone and place()'s "nothing focusable, focus the
12185    //   dialog" branch becomes unreachable — an informational dialog has none by
12186    //   definition, so the loop would spin until close.
12187    //
12188    //   Gate on the DIALOG being visible alone and it fires too early. Measured on the
12189    //   delete-team modal (teams/admin/general, a .sr-modal inside the chassis shell):
12190    //   the dialog is visible 2 frames after open, but its 5 controls do not become
12191    //   visible until ~400ms. Focus landed on the dialog via the fallback instead of on
12192    //   a control — not a leak, since the trap held, but not what the branch is for.
12193    //
12194    // So: take focusables the moment they exist; otherwise wait, and only fall back once
12195    // the dialog has been visible for a grace period and still offers nothing.
12196    var settleFrames = 0;
12197    (function attempt() {
12198      if (stack.indexOf(overlay) === -1) return;            // closed while we waited
12199      if (visibleFocusable(dialog).length) return place();  // the normal case
12200      if (isVisible(dialog) && ++settleFrames > 30) return place(); // genuinely nothing
12201      requestAnimationFrame(attempt);
12202    })();
12203
12204    function place() {
12205      // visibleFocusable, not the raw list: when the dialog IS a form_with element its
12206      // first `input:not([disabled])` is Rails' hidden authenticity_token, and .focus()
12207      // on a hidden input is a no-op — so focus stayed on the trigger, outside the
12208      // overlay, and the first Tab presses walked the page BEHIND the backdrop. The Tab
12209      // handler above already filters this way; only the initial focus did not.
12210      var focusable = visibleFocusable(dialog);
12211      // [data-rm-autofocus] names the safe default (an alertdialog's Cancel); otherwise the first control.
12212      var preferred = dialog.querySelector('[data-rm-autofocus]');
12213      if (preferred && focusable.indexOf(preferred) !== -1) {
12214        preferred.focus();
12215      } else if (focusable.length) {
12216        focusable[0].focus();
12217      } else {
12218        // Nothing focusable inside (a purely informational dialog): focus the dialog
12219        // itself so the trap still has somewhere to hold, rather than leaving the
12220        // keyboard on the page behind it.
12221        if (!dialog.hasAttribute('tabindex')) dialog.setAttribute('tabindex', '-1');
12222        dialog.focus();
12223      }
12224    }
12225  }
12226
12227  // --- safe overlay close: only a genuine click on the backdrop itself --------
12228  // (mousedown+click both on the overlay — prevents a text-selection drag that
12229  // ends outside the dialog from being read as a close).
12230
12231  function bindOverlayOnce(overlay) {
12232    if (overlay.dataset.rmModalBound === 'true') return;
12233    overlay.dataset.rmModalBound = 'true';
12234
12235    var downOnOverlay = false;
12236    overlay.addEventListener('mousedown', function (e) {
12237      downOnOverlay = e.target === overlay;
12238    });
12239    overlay.addEventListener('click', function (e) {
12240      if (e.target === overlay && downOnOverlay) close(overlay);
12241      downOnOverlay = false;
12242    });
12243  }
12244
12245  // --- lookup / AJAX injection (SPEC rule 7: preloaded OR fetched partial) ----
12246
12247  function findOverlay(name) {
12248    return document.querySelector('[data-rm-modal-name="' + name + '"]') ||
12249      document.getElementById(name);
12250  }
12251
12252  function injectFromUrl(url, name) {
12253    return fetch(url, {
12254      headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/html' },
12255      credentials: 'same-origin'
12256    })
12257      .then(function (res) { return res.ok ? res.text() : null; })
12258      .then(function (html) {
12259        if (!html) return null;
12260        var wrap = document.createElement('div');
12261        wrap.innerHTML = html.trim();
12262        var el = wrap.querySelector(SEL.overlay) || wrap.firstElementChild;
12263        if (!el) return null;
12264        el.dataset.rmModalInjected = 'true'; // dropped on turbolinks:before-cache
12265        if (!el.dataset.rmModalName) el.dataset.rmModalName = name;
12266        document.body.appendChild(el);
12267        return el;
12268      })
12269      .catch(function () { return null; });
12270  }
12271
12272  function resolveTarget(idOrEl) {
12273    if (typeof idOrEl === 'string') {
12274      return { name: idOrEl, trigger: document.querySelector('[data-rm-modal="' + idOrEl + '"]') };
12275    }
12276    if (idOrEl instanceof Element) {
12277      if (idOrEl.matches(SEL.overlay)) {
12278        return { name: idOrEl.dataset.rmModalName || idOrEl.id, overlay: idOrEl };
12279      }
12280      if (idOrEl.dataset.rmModal) {
12281        return { name: idOrEl.dataset.rmModal, trigger: idOrEl };
12282      }
12283    }
12284    return null;
12285  }
12286
12287  // --- public API ---------------------------------------------------------------
12288
12289  function open(idOrEl) {
12290    var target = resolveTarget(idOrEl);
12291    if (!target) return;
12292
12293    var overlay = target.overlay || findOverlay(target.name);
12294    if (overlay) {
12295      show(overlay);
12296      return;
12297    }
12298    var url = target.trigger && target.trigger.dataset.rmModalUrl;
12299    if (!url) return; // no preloaded markup, nothing to fetch
12300    injectFromUrl(url, target.name).then(function (el) {
12301      if (el) show(el);
12302    });
12303  }
12304
12305  function show(overlay) {
12306    if (stack.indexOf(overlay) !== -1) return; // already open
12307    returnFocus.set(overlay, document.activeElement);
12308    stack.push(overlay);
12309    bindOverlayOnce(overlay);
12310    overlay.classList.add(OPEN_CLASS);
12311    // Overlays ship aria-hidden="true" in their markup (18 partials do) and nothing
12312    // ever flipped it, so an open modal was invisible to screen readers while being
12313    // the only thing a sighted user could interact with. Owned here, not per-modal:
12314    // every consumer routes through show()/close().
12315    overlay.setAttribute('aria-hidden', 'false');
12316    lockScroll();
12317    activateTrapFor(overlay);
12318    // Content-init hook for consumers (AJAX-injected markup has no other
12319    // lifecycle moment): fires on every open, bubbles to document.
12320    overlay.dispatchEvent(new CustomEvent('rm-modal:open', { bubbles: true }));
12321  }
12322
12323  function close(idOrEl) {
12324    var overlay = idOrEl instanceof Element ? idOrEl : stack[stack.length - 1];
12325    var idx = stack.indexOf(overlay);
12326    if (idx === -1) return;
12327    stack.splice(idx, 1);
12328    overlay.classList.remove(OPEN_CLASS);
12329    overlay.setAttribute('aria-hidden', 'true');
12330
12331    var toRefocus = returnFocus.get(overlay);
12332    returnFocus.delete(overlay);
12333    if (toRefocus && typeof toRefocus.focus === 'function') toRefocus.focus();
12334
12335    if (stack.length) {
12336      activateTrapFor(stack[stack.length - 1]);
12337    } else if (trapCleanup) {
12338      trapCleanup();
12339      trapCleanup = null;
12340    }
12341    unlockScrollIfEmpty();
12342
12343    // Counterpart to rm-modal:open. Without it, migrating a modal onto the chassis
12344    // SILENTLY DROPS its teardown: the chassis newly arms Escape, backdrop click and
12345    // every [data-rm-modal-close] button, and none of those route through the close
12346    // function the consumer used to own, so whatever that function did stops running.
12347    // Two real cases on this branch:
12348    //   - teams/team_settings.js cleared the "type the team name" confirmation and
12349    //     re-disabled Delete. Dismissed by backdrop, the modal reopened with the name
12350    //     still typed and Delete still ENABLED.
12351    //   - teams/gallery.js removed .show from the album-select modal. Dismissed by
12352    //     Escape, the overlay lost --open and gained aria-hidden="true" while .show
12353    //     kept it on screen: a visible dialog announced as hidden.
12354    // Fires before the injected-overlay removal below, so a listener can still read
12355    // the overlay's content.
12356    overlay.dispatchEvent(new CustomEvent('rm-modal:close', { bubbles: true }));
12357
12358    // AJAX-injected overlays are dropped on close so the next open()
12359    // re-fetches: their content reflects server state that can change while
12360    // the page stays mounted (e.g. captain status), and hiding would serve
12361    // it stale. Wait out the closing transition before removing.
12362    if (overlay.dataset.rmModalInjected === 'true') {
12363      setTimeout(function () {
12364        // skip if a rapid re-open put it back on the stack mid-transition
12365        if (stack.indexOf(overlay) === -1) overlay.remove();
12366      }, 250);
12367    }
12368  }
12369
12370  function bindTriggers() {
12371    if (triggersBound) return; // document persists across Turbolinks navigations
12372    triggersBound = true;
12373
12374    document.addEventListener('click', function (e) {
12375      var closeBtn = e.target.closest('[data-rm-modal-close]');
12376      if (closeBtn) {
12377        var overlay = closeBtn.closest(SEL.overlay);
12378        if (overlay) close(overlay);
12379        return;
12380      }
12381      var trigger = e.target.closest('[data-rm-modal]');
12382      if (trigger) {
12383        e.preventDefault();
12384        open(trigger);
12385      }
12386    });
12387
12388    document.addEventListener('keydown', function (e) {
12389      if (e.key === 'Escape' && stack.length) close();
12390    });
12391  }
12392
12393  // trapFocus is exported because two dialogs cannot use open()/close(): the Stripe
12394  // donate checkout and the perk reader are hand-rolled `hidden`-toggled overlays whose
12395  // markup this train deliberately did not restyle. They still need a real focus trap,
12396  // and a second copy of one is how traps drift apart — this is the same trap the
12397  // chassis runs, minus the open/close it cannot drive.
12398  //   var release = RmModal.trapFocus(dialogEl);   // ... release() on close
12399  window.RmModal = {
12400    open: open,
12401    close: close,
12402    bindTriggers: bindTriggers,
12403    trapFocus: trapFocus
12404  };
12405
12406  // --- Turbolinks lifecycle ------------------------------------------------------
12407
12408  document.addEventListener('turbolinks:load', bindTriggers);
12409
12410  document.addEventListener('turbolinks:before-cache', function () {
12411    // Never cache a page mid-modal, and drop AJAX-injected overlays so the
12412    // next open() re-fetches fresh content instead of stale cached state.
12413    stack.slice().forEach(function (overlay) { close(overlay); });
12414    document.querySelectorAll('[data-rm-modal-injected="true"]').forEach(function (el) { el.remove(); });
12415  });
12416
12417  // Non-Turbolinks first load.
12418  if (document.readyState === 'loading') {
12419    document.addEventListener('DOMContentLoaded', bindTriggers);
12420  } else {
12421    bindTriggers();
12422  }
12423})();
12424// RmConfirm: the rm-modal confirm for destructive actions, replacing the native confirm()
12425// (DESIGN-STANDARDS rule 5). Mark a form with the message; its submit asks first.
12426//
12427//   <form data-rm-confirm="Delete this reply?" data-rm-confirm-ok="Delete reply">...</form>
12428//   (button_to ..., form: { data: { rm_confirm: '...', rm_confirm_ok: 'Delete reply' } })
12429//   A link inside another form (forms do not nest): <a href="/x" data-rm-confirm-link="Delete?" data-rm-method="delete">
12430// or from script: RmConfirm.confirm('Hide this review?', 'Hide review', function () { ... });
12431//
12432// One overlay is built on first use and reused. Escape, the backdrop, Cancel and the X close it
12433// through RmModal; OK re-submits the form once.
12434(function () {
12435  'use strict';
12436  if (window.RmConfirm) return;
12437
12438  var overlay = null;
12439  var pending = null;
12440
12441  function build() {
12442    overlay = document.createElement('div');
12443    overlay.className = 'rm-modal__overlay';
12444    overlay.setAttribute('data-rm-modal-name', 'rm-confirm');
12445    overlay.innerHTML =
12446      '<div class="rm-modal__dialog rm-modal__dialog--sm" role="alertdialog" aria-modal="true" aria-labelledby="rmConfirmTitle">' +
12447        '<div class="rm-modal__header">' +
12448          '<div><h2 class="rm-modal__title" id="rmConfirmTitle"></h2></div>' +
12449          '<button type="button" class="rm-modal__close" data-rm-modal-close aria-label="Close"><i class="fa-solid fa-xmark"></i></button>' +
12450        '</div>' +
12451        '<div class="rm-modal__footer">' +
12452          '<button type="button" class="rm-modal__cancel" data-rm-modal-close data-rm-autofocus>Cancel</button>' +
12453          '<button type="button" class="racemob-btn-danger" data-rm-confirm-ok></button>' +
12454        '</div>' +
12455      '</div>';
12456    document.body.appendChild(overlay);
12457    overlay.querySelector('[data-rm-confirm-ok]').addEventListener('click', function () {
12458      var onOk = pending;
12459      pending = null;
12460      window.RmModal.close(overlay);
12461      if (onOk) onOk();
12462    });
12463  }
12464
12465  // message / okLabel -> the dialog; onOk runs once if they confirm. Cancel leaves everything as it was.
12466  function confirmThen(message, okLabel, onOk) {
12467    if (!overlay) build();
12468    pending = onOk;
12469    overlay.querySelector('.rm-modal__title').textContent = message;
12470    overlay.querySelector('[data-rm-confirm-ok]').textContent = okLabel || 'Delete';
12471    window.RmModal.open(overlay);
12472  }
12473
12474  function ask(form) {
12475    confirmThen(form.getAttribute('data-rm-confirm'), form.getAttribute('data-rm-confirm-ok'), function () {
12476      form.setAttribute('data-rm-confirmed', 'true');
12477      form.requestSubmit();
12478      // Cleared at once: a bfcache Back to this page must ask again, not delete unasked.
12479      form.removeAttribute('data-rm-confirmed');
12480    });
12481  }
12482
12483  document.addEventListener('submit', function (e) {
12484    var form = e.target;
12485    if (!form.getAttribute || !form.getAttribute('data-rm-confirm')) return;
12486    if (form.getAttribute('data-rm-confirmed') === 'true') return; // the confirmed re-submit goes through
12487    e.preventDefault();
12488    ask(form);
12489  });
12490
12491  // A confirmed link submits a real form (the method override and CSRF token ride along).
12492  function submitLink(href, method) {
12493    var token = document.querySelector('meta[name="csrf-token"]');
12494    var form = document.createElement('form');
12495    form.method = 'post';
12496    form.action = href;
12497    form.hidden = true;
12498    [['_method', method], ['authenticity_token', token ? token.getAttribute('content') : '']].forEach(function (pair) {
12499      var input = document.createElement('input');
12500      input.type = 'hidden';
12501      input.name = pair[0];
12502      input.value = pair[1];
12503      form.appendChild(input);
12504    });
12505    document.body.appendChild(form);
12506    form.submit();
12507  }
12508
12509  document.addEventListener('click', function (e) {
12510    var link = e.target.closest && e.target.closest('[data-rm-confirm-link]');
12511    if (!link) return;
12512    e.preventDefault();
12513    confirmThen(link.getAttribute('data-rm-confirm-link'), link.getAttribute('data-rm-confirm-ok'), function () {
12514      submitLink(link.href, link.getAttribute('data-rm-method') || 'post');
12515    });
12516  });
12517
12518  window.RmConfirm = { ask: ask, confirm: confirmThen };
12519})();
12520/* RaceMob follow bar — the ONE shared IntersectionObserver for .rm-followbar
12521   (chassis component, Kevin 2026-07-07 round 9). Production landing of the frozen
12522   kit's RmFollowbar (prototypes/themes/kit.js): PLAYBOOK registry — "lands with
12523   first section swap". Also carries the tab-rail scroll-into-view (390 clip fix).
12524
12525   Turbolinks-safe: idempotent init on turbolinks:load, observers disconnected on
12526   turbolinks:before-cache so a bfcache restore does not leak or double-bind.
12527
12528   Usage:
12529     <div class="rm-followbar" data-rm-followbar=".r-hero"> … </div>
12530   Attribute value = the page hero selector (defaults to .rm-hero). The bar gets
12531   .is-visible while the hero is fully out of the viewport. Desktop AND mobile. */
12532
12533(function () {
12534  var observers = [];
12535
12536  function initFollowbar() {
12537    if (!('IntersectionObserver' in window)) return;
12538    document.querySelectorAll('.rm-followbar[data-rm-followbar]').forEach(function (bar) {
12539      if (bar.dataset.rmFollowbarBound === '1') return;
12540      var hero = document.querySelector(bar.getAttribute('data-rm-followbar') || '.rm-hero');
12541      if (!hero) return;
12542      var obs = new IntersectionObserver(function (entries) {
12543        bar.classList.toggle('is-visible', !entries[0].isIntersecting);
12544      }, { threshold: 0 });
12545      obs.observe(hero);
12546      observers.push(obs);
12547      bar.dataset.rmFollowbarBound = '1';
12548    });
12549  }
12550
12551  /* When a horizontally-scrolling .rm-tabs rail overflows, scroll its active tab
12552     into view so a server-rendered active tab is never clipped off the right edge
12553     (390 fade affordance). Only moves the rail's own scrollLeft, never the page. */
12554  function initTabScroll() {
12555    document.querySelectorAll('.rm-tabs').forEach(function (rail) {
12556      var active = rail.querySelector('.rm-tabs__tab.is-active');
12557      if (!active || rail.scrollWidth <= rail.clientWidth) return;
12558      var railRect = rail.getBoundingClientRect();
12559      var activeRect = active.getBoundingClientRect();
12560      rail.scrollLeft += (activeRect.left - railRect.left) - 16;
12561    });
12562  }
12563
12564  function init() {
12565    initFollowbar();
12566    initTabScroll();
12567  }
12568
12569  function teardown() {
12570    observers.forEach(function (o) { o.disconnect(); });
12571    observers = [];
12572    document.querySelectorAll('.rm-followbar[data-rm-followbar]').forEach(function (bar) {
12573      delete bar.dataset.rmFollowbarBound;
12574    });
12575  }
12576
12577  document.addEventListener('turbolinks:load', init);
12578  document.addEventListener('turbolinks:before-cache', teardown);
12579  // Non-Turbolinks fallback (direct load / no Turbolinks on page).
12580  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
12581  else init();
12582})();
12583// components/auth_gate — turns a would-be redirect into the contextual auth modal.
12584//
12585// Anonymous visitors on the team hub used to be thrown to
12586// /onboarding/start?switch_to=signin the moment they clicked Photos, Discussions or
12587// Announcements: a whole-page bounce out of the team they were reading, with the team
12588// nowhere in sight when they landed. Race Hub already answers this with AuthModal
12589// (races/_hero, _reviews, _discounts); this is the same answer, wired once instead of
12590// per control.
12591//
12592// Markup contract: any anchor or button the server knows will bounce an anonymous user
12593// carries data-auth-gate="<what they were trying to do>". The server-side require_login
12594// redirect stays exactly as it was — it is the no-JS fallback, and it still guards the
12595// URL if someone types it.
12596//
12597// ponytail: one delegated listener on document, no per-control wiring.
12598(function () {
12599  'use strict';
12600
12601  function openGate(el, event) {
12602    if (!window.AuthModal || !window.AuthModal.show) return; // let the redirect happen
12603
12604    event.preventDefault();
12605
12606    var intent = el.getAttribute('data-auth-gate') || 'continue';
12607    var destination = el.getAttribute('href');
12608
12609    window.AuthModal.show({
12610      defaultTab: 'signin',
12611      title: 'Sign in to continue',
12612      subtitle: intent,
12613      benefits: false,
12614      // Land on what they clicked, not back on the page they clicked from.
12615      postAuthUrl: destination && destination !== '#' ? destination : null
12616    });
12617  }
12618
12619  document.addEventListener('click', function (event) {
12620    var el = event.target.closest ? event.target.closest('[data-auth-gate]') : null;
12621    if (!el) return;
12622    openGate(el, event);
12623  }, true);
12624})();
12625// RmFollowToggle: the shared Follow / Following button (PUT /:username/follow|unfollow).
12626// Extracted verbatim from race_hub.js (the Going tab) so the athlete profile's hero can use it too.
12627//
12628//   <button type="button" class="racemob-btn-secondary racemob-btn--sm"
12629//           data-follow-toggle="<username>" data-following="false"><i class="fa-solid fa-user-plus"></i> Follow</button>
12630//
12631// One delegated click handler. On success it flips data-following and repaints the label and the
12632// teal / secondary button class; on failure it toasts and leaves the button as it was.
12633//
12634// Optional attributes (the community widget, #444; absent = the original behaviour exactly):
12635//   data-follow-approval="true"  the subject approves followers: idle label reads "Request to follow",
12636//                                and the server answers action "requested" instead of "followed".
12637//   data-follow-pending="true"   a request is waiting: label "Requested", click cancels it (confirm first).
12638//   data-follow-confirm="Unfollow Sam?"  ask through RmConfirm before an unfollow / cancel.
12639//   data-follow-name="Sam"       used in the toast copy.
12640//   data-follow-primary          idle states wear the primary (orange) button, not secondary.
12641//   data-auth-gate               anon: components/auth_gate.js opens the sign-in modal instead.
12642(function () {
12643  'use strict';
12644  if (window.RmFollowToggle) return;
12645
12646  var LABEL = {
12647    follow: '<i class="fa-solid fa-user-plus"></i> Follow',
12648    request: '<i class="fa-solid fa-user-plus"></i> Request to follow',
12649    requested: '<i class="fa-regular fa-clock"></i> Requested',
12650    following: '<i class="fa-solid fa-check"></i> Following'
12651  };
12652
12653  function toast(message, type) {
12654    if (window.RmToast) { window.RmToast.show(message, type || 'info'); }
12655  }
12656
12657  function csrf() {
12658    var m = document.querySelector('meta[name="csrf-token"]');
12659    return (m && m.content) || window.csrfToken || '';
12660  }
12661
12662  function paint(btn, state) {
12663    // Glass is OPT-IN (data-follow-glass, the athlete hero only: there the secondary button is ink on ink and
12664    // invisible). Every other button keeps master's classes: primary on the community widget's idle state, otherwise
12665    // secondary (the Race Hub Going rows), teal when following.
12666    var primary = btn.hasAttribute('data-follow-primary');
12667    var glass = btn.hasAttribute('data-follow-glass');
12668    var idle = glass ? 'racemob-btn-ghost racemob-btn--glass' : (primary ? 'racemob-btn-primary' : 'racemob-btn-secondary');
12669    var requested = (glass ? 'racemob-btn-ghost racemob-btn--glass' : 'racemob-btn-secondary') + ' rm-follow--requested';
12670    var cls = { follow: idle, request: idle, requested: requested, following: 'racemob-btn-teal' }[state];
12671    btn.setAttribute('data-following', String(state === 'following'));
12672    if (state === 'requested') { btn.setAttribute('data-follow-pending', 'true'); } else { btn.removeAttribute('data-follow-pending'); }
12673    btn.className = 'racemob-btn--sm ' + cls;
12674    btn.innerHTML = LABEL[state];
12675  }
12676
12677  function idleState(btn) {
12678    return btn.getAttribute('data-follow-approval') === 'true' ? 'request' : 'follow';
12679  }
12680
12681  function send(btn) {
12682    var following = btn.getAttribute('data-following') === 'true';
12683    var pending = btn.getAttribute('data-follow-pending') === 'true';
12684    var user = btn.getAttribute('data-follow-toggle');
12685    var name = btn.getAttribute('data-follow-name') || 'this runner';
12686    var verbose = btn.hasAttribute('data-follow-confirm'); // the community widget confirms in toasts too
12687    btn.disabled = true;
12688    fetch('/' + encodeURIComponent(user) + (following || pending ? '/unfollow' : '/follow'), {
12689      method: 'PUT', credentials: 'same-origin',
12690      headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-Token': csrf() }
12691    }).then(function (r) {
12692      if (r.ok) { return r.json().catch(function () { return {}; }); }
12693      return r.json().catch(function () { return {}; }).then(function (body) {
12694        var err = new Error(body.error || 'status ' + r.status);
12695        err.fromServer = !!body.error;
12696        throw err;
12697      });
12698    }).then(function (body) {
12699      if (following || pending) {
12700        paint(btn, idleState(btn));
12701        if (verbose) { toast(pending ? 'Request cancelled' : 'Unfollowed ' + name, 'success'); }
12702      } else if (body.action === 'requested') {
12703        paint(btn, 'requested');
12704        toast('Request sent to ' + name, 'success');
12705      } else {
12706        paint(btn, 'following');
12707        if (verbose) { toast('You are now following ' + name, 'success'); }
12708      }
12709    }).catch(function (err) {
12710      toast(err.fromServer ? err.message : 'Follow did not go through. Try again.', 'error');
12711    }).then(function () { btn.disabled = false; });
12712  }
12713
12714  function follow(btn) {
12715    var ask = btn.getAttribute('data-follow-confirm');
12716    var pending = btn.getAttribute('data-follow-pending') === 'true';
12717    var undoing = pending || btn.getAttribute('data-following') === 'true';
12718    if (ask && undoing && window.RmConfirm) {
12719      window.RmConfirm.confirm(pending ? 'Cancel your request?' : ask, pending ? 'Cancel request' : 'Unfollow', function () { send(btn); });
12720      return;
12721    }
12722    send(btn);
12723  }
12724
12725  document.addEventListener('click', function (e) {
12726    var btn = e.target.closest && e.target.closest('[data-follow-toggle]');
12727    if (!btn) return;
12728    if (btn.hasAttribute('data-auth-gate')) return; // auth_gate.js (capture phase) owns anon clicks
12729    e.preventDefault();
12730    follow(btn);
12731  });
12732
12733  window.RmFollowToggle = { toggle: follow };
12734})();
12735// RmToast — design-system toast component (SPEC.md step 2).
12736// Chassis: fitness_tracker/ui_helpers.js UIHelpers.toast(), retargeted at .rm-toast.
12737// Not required by any bundle yet — isolated build, wired in at adoption time.
12738//
12739// window.RmToast.show(message, type, opts)
12740//   type: 'success' | 'error' | 'info' (default 'info')
12741//   opts.duration: ms before auto-dismiss, 0 = sticky (default 3000)
12742
12743(function () {
12744  'use strict';
12745
12746  var ICONS = {
12747    success: '<svg viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
12748    error: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
12749    info: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>'
12750  };
12751  var CLOSE_ICON = '<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
12752  var DISMISS_MS = 200; // must match the .rm-toast transition duration
12753
12754  var container = null;
12755  var timers = new Set(); // pending timeouts; cleared on turbolinks:before-cache
12756  var bound = false; // double-init guard for the delegated click listener
12757
12758  function getContainer() {
12759    if (!container || !document.body.contains(container)) {
12760      container = document.createElement('div');
12761      container.className = 'rm-toast-container';
12762      document.body.appendChild(container);
12763    }
12764    return container;
12765  }
12766
12767  function dismiss(toast) {
12768    if (!toast.classList.contains('is-visible')) return; // already dismissing
12769    toast.classList.remove('is-visible');
12770    var t = setTimeout(function () {
12771      timers.delete(t);
12772      toast.remove();
12773    }, DISMISS_MS);
12774    timers.add(t);
12775  }
12776
12777  function onDocumentClick(e) {
12778    var closeBtn = e.target.closest && e.target.closest('[data-rm-toast-close]');
12779    if (!closeBtn) return;
12780    var toast = closeBtn.closest('.rm-toast');
12781    if (toast) dismiss(toast);
12782  }
12783
12784  function show(message, type, opts) {
12785    type = ICONS[type] ? type : 'info';
12786    opts = opts || {};
12787    var duration = opts.duration != null ? opts.duration : 3000;
12788
12789    var toast = document.createElement('div');
12790    toast.className = 'rm-toast rm-toast--' + type;
12791    toast.setAttribute('role', 'status');
12792    toast.setAttribute('aria-live', 'polite');
12793    toast.setAttribute('data-rm-toast', '');
12794
12795    var icon = document.createElement('span');
12796    icon.className = 'rm-toast__icon';
12797    icon.setAttribute('aria-hidden', 'true');
12798    icon.innerHTML = ICONS[type];
12799
12800    var msg = document.createElement('span');
12801    msg.className = 'rm-toast__message';
12802    msg.textContent = message; // textContent only: caller message is never rendered as HTML
12803
12804    var close = document.createElement('button');
12805    close.type = 'button';
12806    close.className = 'rm-toast__close';
12807    close.setAttribute('aria-label', 'Dismiss');
12808    close.setAttribute('data-rm-toast-close', '');
12809    close.innerHTML = CLOSE_ICON;
12810
12811    toast.appendChild(icon);
12812    toast.appendChild(msg);
12813    toast.appendChild(close);
12814    getContainer().appendChild(toast);
12815
12816    requestAnimationFrame(function () {
12817      toast.classList.add('is-visible');
12818    });
12819
12820    if (duration > 0) {
12821      var t = setTimeout(function () {
12822        timers.delete(t);
12823        dismiss(toast);
12824      }, duration);
12825      timers.add(t);
12826    }
12827
12828    return toast;
12829  }
12830
12831  function init() {
12832    if (bound) return;
12833    document.addEventListener('click', onDocumentClick);
12834    bound = true;
12835  }
12836
12837  function teardown() {
12838    // Turbolinks snapshots the live DOM on navigation away; a stale toast
12839    // stack baked into that cache would flash back on history nav. Drop it
12840    // and stop the delegated listener until the next turbolinks:load.
12841    document.removeEventListener('click', onDocumentClick);
12842    bound = false;
12843    timers.forEach(clearTimeout);
12844    timers.clear();
12845    if (container) {
12846      container.remove();
12847      container = null;
12848    }
12849  }
12850
12851  document.addEventListener('turbolinks:load', init);
12852  document.addEventListener('turbolinks:before-cache', teardown);
12853  init(); // covers first paint / non-Turbolinks pages before turbolinks:load fires
12854
12855  window.RmToast = { show: show };
12856})();
12857// ==========================================================================
12858// Community widget JS (PROTO-483): comment, high-five, comment report, requests inbox.
12859// Follow is NOT here: it is RmFollowToggle (components/follow_toggle.js), extended with the
12860// request states. fetch + JSON (no rails-ujs, no remote:true). State machine:
12861//   idle -> pending (visible, disabled) -> done (UI updates from the 2xx body)
12862//        -> error (revert + toast)   |   gated (anon: sign-in modal via components/auth_gate.js)
12863// NOTHING completes in the UI before the server confirms (GRILL-478 seed #11).
12864// Feedback is RmToast, confirms are RmConfirm, the report dialog is rm-modal.
12865// Event delegation bound once on document.
12866// ==========================================================================
12867
12868(function () {
12869  'use strict';
12870  if (window.RmCommunity) return;
12871
12872  // The one toast (components/toast); success by default.
12873  function rmToast(msg, kind) {
12874    if (window.RmToast) { window.RmToast.show(msg, kind || 'success'); }
12875  }
12876
12877  // ---- fetch helper ------------------------------------------------------
12878  function csrf() {
12879    var m = document.querySelector('meta[name="csrf-token"]');
12880    return (m && m.content) || window.csrfToken || '';
12881  }
12882
12883  // Resolves to parsed JSON (or {}); rejects on non-2xx so callers revert.
12884  function api(url, method, params) {
12885    var opts = {
12886      method: method,
12887      headers: {
12888        'X-CSRF-Token': csrf(),
12889        'X-Requested-With': 'XMLHttpRequest',
12890        'Accept': 'application/json'
12891      },
12892      credentials: 'same-origin'
12893    };
12894    if (params) {
12895      opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
12896      opts.body = Object.keys(params).map(function (k) {
12897        return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]);
12898      }).join('&');
12899    }
12900    return fetch(url, opts).then(function (res) {
12901      if (!res.ok) {
12902        return res.json().catch(function () { return {}; }).then(function (body) {
12903          var err = new Error(body.error || 'Request failed');
12904          err.status = res.status;
12905          throw err;
12906        });
12907      }
12908      if (res.status === 204) return {};
12909      return res.json().catch(function () { return {}; });
12910    });
12911  }
12912
12913  function esc(text) {
12914    var d = document.createElement('div');
12915    d.textContent = text == null ? '' : text;
12916    return d.innerHTML;
12917  }
12918
12919  function widgetOf(el) { return el.closest('[data-rm-widget]'); }
12920
12921  // Anon: components/auth_gate.js opens the sign-in modal in the capture phase and stops here.
12922  // If AuthModal never loaded, fall back to the sign-up page.
12923  function gate() {
12924    if (!window.AuthModal || !window.AuthModal.show) { window.location.href = '/signup'; }
12925  }
12926
12927  // =======================================================================
12928  // HIGH-FIVE
12929  // =======================================================================
12930  function handleHighFive(btn) {
12931    if (btn.hasAttribute('data-auth-gate')) { gate(); return; }
12932    if (btn.disabled) return;
12933
12934    var on = btn.dataset.on === 'true';
12935    var vType = btn.dataset.votableType;
12936    var vId = btn.dataset.votableId;
12937    var count = parseInt(btn.dataset.count || '0', 10);
12938
12939    btn.disabled = true;
12940    btn.dataset.busy = 'true';
12941    var restore = btn.innerHTML;
12942    btn.innerHTML = '<i class="fa-solid fa-circle-notch cw-spin"></i> ' + count;
12943
12944    var req = on
12945      ? api('/votes/0?v_type=' + encodeURIComponent(vType) + '&v_id=' + encodeURIComponent(vId), 'DELETE')
12946      : api('/votes', 'POST', { v_type: vType, v_id: vId });
12947
12948    req.then(function (body) {
12949      var liked = !!body.liked;
12950      var newCount = (typeof body.count === 'number') ? body.count : (liked ? count + 1 : Math.max(0, count - 1));
12951      btn.dataset.on = liked ? 'true' : 'false';
12952      btn.dataset.count = String(newCount);
12953      btn.disabled = false;
12954      btn.dataset.busy = 'false';
12955      btn.classList.toggle('is-on', liked);
12956      btn.innerHTML = '<i class="fa-solid fa-hands-clapping"></i> ' + (liked ? 'High-fived · ' : 'High five · ') + newCount;
12957      updateHighFiveWho(btn, liked);
12958      rmToast(liked ? 'High five sent' : 'High five removed');
12959    }).catch(function () {
12960      btn.disabled = false;
12961      btn.dataset.busy = 'false';
12962      btn.innerHTML = restore;
12963      rmToast('High five didn’t stick. Try again.', 'error');
12964    });
12965  }
12966
12967  // After confirm, reflect "You" in the pile (post-2xx, not optimistic).
12968  function updateHighFiveWho(btn, liked) {
12969    var widget = widgetOf(btn);
12970    if (!widget) return;
12971    var pile = widget.querySelector('[data-rm-hf-pile]');
12972    if (!pile) return;
12973    var you = pile.querySelector('[data-rm-hf-you]');
12974    if (liked && !you) {
12975      you = document.createElement('span');
12976      you.className = 'cw-av cw-av--pile cw-av--you';
12977      you.setAttribute('data-rm-hf-you', '');
12978      you.textContent = 'You';
12979      pile.appendChild(you);
12980    } else if (!liked && you) {
12981      you.remove();
12982    }
12983  }
12984
12985  // =======================================================================
12986  // COMMENTS
12987  // =======================================================================
12988  function handleCommentSubmit(widget) {
12989    var input = widget.querySelector('[data-rm-comment-input]');
12990    var btn = widget.querySelector('[data-rm-comment-submit]');
12991    if (!input || !btn) return;
12992    var text = input.value.trim();
12993    if (!text) return;
12994    if (btn.dataset.busy === 'true') return;
12995
12996    btn.dataset.busy = 'true';
12997    btn.disabled = true;
12998    input.disabled = true;
12999    var restore = btn.innerHTML;
13000    btn.innerHTML = '<i class="fa-solid fa-circle-notch cw-spin"></i>';
13001
13002    api('/comments', 'POST', {
13003      'comment[commentable_type]': widget.dataset.commentableType,
13004      'comment[commentable_id]': widget.dataset.commentableId,
13005      'comment[comment]': text
13006    }).then(function (body) {
13007      // Appended ONLY from the server response — no optimistic insert.
13008      appendComment(widget, body.comment);
13009      input.value = '';
13010      input.disabled = false;
13011      btn.dataset.busy = 'false';
13012      btn.disabled = false;
13013      btn.innerHTML = restore;
13014      rmToast('Comment posted');
13015    }).catch(function (err) {
13016      // Input preserved, no phantom comment.
13017      input.disabled = false;
13018      btn.dataset.busy = 'false';
13019      btn.disabled = false;
13020      btn.innerHTML = restore;
13021      rmToast(err.message || 'Comment didn’t post. Your text is still here.', 'error');
13022    });
13023  }
13024
13025  function appendComment(widget, c) {
13026    if (!c) return;
13027    var list = widget.querySelector('[data-rm-comment-list]');
13028    if (!list) return;
13029    var empty = list.querySelector('[data-rm-comment-empty]');
13030    if (empty) empty.remove();
13031
13032    var u = c.user || {};
13033    var initials = (u.name || 'You').trim().slice(0, 2).toUpperCase();
13034    var item = document.createElement('div');
13035    item.className = 'cw-item';
13036    item.setAttribute('data-comment-id', c.id);
13037    item.innerHTML =
13038      '<span class="cw-av cw-av--sm">
13038' + esc(initials) + '</span>' +
13039      '<div class="cw-item__body">' +
13040        '<div class="cw-item__meta">' +
13041          '<span class="cw-item__name">' + esc(u.name || 'You') + '</span>' +
13042          '<span class="cw-item__when">now</span>' +
13043        '</div>' +
13044        '<div class="cw-item__text">' + esc(c.comment) + '</div>' +
13045        '<button class="cw-kebab" data-rm-kebab aria-label="Comment options"><i class="fa-solid fa-ellipsis"></i></button>' +
13046        '<div class="cw-menu" data-rm-menu hidden>' +
13047          '<button class="is-danger" data-rm-comment-delete data-comment-id="' + esc(c.id) + '"><i class="fa-regular fa-trash-can"></i> Delete</button>' +
13048        '</div>' +
13049      '</div>';
13050    list.appendChild(item);
13051    bumpCommentCount(widget, 1);
13052  }
13053
13054  function handleCommentDelete(btn) {
13055    var widget = widgetOf(btn);
13056    var id = btn.dataset.commentId;
13057    var item = btn.closest('.cw-item');
13058    closeMenus();
13059    window.RmConfirm.confirm('Delete this comment?', 'Delete comment', function () {
13060      if (item) item.classList.add('is-busy');
13061      api('/comments/' + encodeURIComponent(id), 'DELETE').then(function () {
13062        if (item) item.remove();
13063        bumpCommentCount(widget, -1);
13064        rmToast('Comment deleted');
13065      }).catch(function () {
13066        if (item) item.classList.remove('is-busy');
13067        rmToast('Couldn’t delete. Try again.', 'error');
13068      });
13069    });
13070  }
13071
13072  function bumpCommentCount(widget, delta) {
13073    var el = widget && widget.querySelector('[data-rm-comment-count]');
13074    if (!el) return;
13075    var n = Math.max(0, (parseInt(el.textContent, 10) || 0) + delta);
13076    el.textContent = n ? String(n) : '';
13077    var empty = widget.querySelector('[data-rm-comment-list]');
13078    if (empty && n === 0 && !empty.querySelector('[data-rm-comment-empty]')) {
13079      var e = document.createElement('div');
13080      e.className = 'cw-empty';
13081      e.setAttribute('data-rm-comment-empty', '');
13082      e.textContent = 'No comments yet.';
13083      empty.appendChild(e);
13084    }
13085  }
13086
13087  // =======================================================================
13088  // REPORT (comment-level intake — persists even before the admin queue)
13089  // =======================================================================
13090  var REASONS = [
13091    ['spam', 'Spam'],
13092    ['harassment', 'Harassment or bullying'],
13093    ['inappropriate', 'Inappropriate content'],
13094    ['other', 'Something else']
13095  ];
13096
13097  var reportOverlay = null;
13098  var reportCommentId = null;
13099
13100  function buildReport() {
13101    reportOverlay = document.createElement('div');
13102    reportOverlay.className = 'rm-modal__overlay';
13103    reportOverlay.setAttribute('data-rm-modal-name', 'rm-report');
13104    var opts = REASONS.map(function (r) {
13105      return '<button type="button" class="cw-reasons__opt" data-rm-report-reason="' + r[0] + '">' + esc(r[1]) + '</button>';
13106    }).join('');
13107    reportOverlay.innerHTML =
13108      '<div class="rm-modal__dialog rm-modal__dialog--sm" role="dialog" aria-modal="true" aria-labelledby="rmReportTitle">' +
13109        '<div class="rm-modal__header">' +
13110          '<div><h2 class="rm-modal__title" id="rmReportTitle">Report this comment</h2>' +
13111          '<p class="rm-modal__subtitle">Our team reviews every report.</p></div>' +
13112          '<button type="button" class="rm-modal__close" data-rm-modal-close aria-label="Close"><i class="fa-solid fa-xmark"></i></button>' +
13113        '</div>' +
13114        '<div class="rm-modal__body"><div class="cw-reasons">' + opts + '</div></div>' +
13115        '<div class="rm-modal__footer"><button type="button" class="rm-modal__cancel" data-rm-modal-close>Cancel</button></div>' +
13116      '</div>';
13117    document.body.appendChild(reportOverlay);
13118    reportOverlay.addEventListener('click', function (e) {
13119      var opt = e.target.closest('[data-rm-report-reason]');
13120      if (!opt) return;
13121      var commentId = reportCommentId;
13122      window.RmModal.close(reportOverlay);
13123      api('/reports', 'POST', { reportable_type: 'Comment', reportable_id: commentId, reason: opt.getAttribute('data-rm-report-reason') })
13124        .then(function () { rmToast('Reported. Our team will take a look.'); })
13125        .catch(function () { rmToast('Couldn’t send the report. Try again.', 'error'); });
13126    });
13127  }
13128
13129  function openReportSheet(commentId) {
13130    closeMenus();
13131    if (!reportOverlay) buildReport();
13132    reportCommentId = commentId;
13133    window.RmModal.open(reportOverlay);
13134  }
13135
13136  // =======================================================================
13137  // REQUESTS INBOX (approve / decline)
13138  // =======================================================================
13139  function handleInbox(btn, approve) {
13140    var row = btn.closest('[data-rm-inbox-row]');
13141    var username = btn.dataset.username;
13142    var name = (btn.dataset.name || 'They');
13143    btn.disabled = true;
13144    if (approve) btn.innerHTML = '<i class="fa-solid fa-circle-notch cw-spin"></i>';
13145    var url = '/fitness/connections/requests/' + encodeURIComponent(username) + (approve ? '/approve' : '/decline');
13146    api(url, 'POST').then(function () {
13147      if (row) row.remove();
13148      syncInbox();
13149      if (approve) rmToast(name.split(' ')[0] + ' is now a follower');
13150      // decline is silent (JC-7)
13151    }).catch(function () {
13152      btn.disabled = false;
13153      if (approve) btn.innerHTML = 'Approve';
13154      rmToast('Couldn’t update. Try again.', 'error');
13155    });
13156  }
13157
13158  function syncInbox() {
13159    document.querySelectorAll('[data-rm-inbox]').forEach(function (inbox) {
13160      var rows = inbox.querySelectorAll('[data-rm-inbox-row]').length;
13161      var count = inbox.querySelector('[data-rm-inbox-count]');
13162      if (count) count.textContent = '(' + rows + ')';
13163      var empty = inbox.querySelector('[data-rm-inbox-empty]');
13164      if (empty) empty.hidden = rows !== 0;
13165    });
13166  }
13167
13168  // =======================================================================
13169  // MENUS
13170  // =======================================================================
13171  function closeMenus() {
13172    document.querySelectorAll('[data-rm-menu]').forEach(function (m) { m.hidden = true; });
13173  }
13174
13175  function toggleMenu(kebab) {
13176    var menu = kebab.parentNode.querySelector('[data-rm-menu]');
13177    if (!menu) return;
13178    var wasHidden = menu.hidden;
13179    closeMenus();
13180    menu.hidden = !wasHidden;
13181  }
13182
13183  // =======================================================================
13184  // DELEGATION (bound once)
13185  // =======================================================================
13186  function onClick(e) {
13187    var t = e.target.closest('button, a, [data-rm-kebab]');
13188    if (!t) { closeMenus(); return; }
13189
13190    if (t.hasAttribute('data-rm-hf')) { e.preventDefault(); handleHighFive(t); return; }
13191    if (t.hasAttribute('data-rm-comment-submit')) { e.preventDefault(); handleCommentSubmit(widgetOf(t)); return; }
13192    if (t.hasAttribute('data-rm-comment-delete')) { e.preventDefault(); handleCommentDelete(t); return; }
13193    if (t.hasAttribute('data-rm-comment-report')) { e.preventDefault(); openReportSheet(t.dataset.commentId); return; }
13194    if (t.hasAttribute('data-rm-kebab')) { e.preventDefault(); e.stopPropagation(); toggleMenu(t); return; }
13195    if (t.hasAttribute('data-rm-compose-gate')) { e.preventDefault(); gate(); return; }
13196    if (t.hasAttribute('data-rm-approve')) { e.preventDefault(); handleInbox(t, true); return; }
13197    if (t.hasAttribute('data-rm-decline')) { e.preventDefault(); handleInbox(t, false); return; }
13198
13199    closeMenus();
13200  }
13201
13202  function onKeydown(e) {
13203    if (e.key !== 'Enter') return;
13204    var input = e.target.closest('[data-rm-comment-input]');
13205    if (!input) return;
13206    e.preventDefault();
13207    handleCommentSubmit(widgetOf(input));
13208  }
13209
13210  function bindOnce() {
13211    if (document.documentElement.dataset.rmCommunityBound === 'true') return;
13212    document.documentElement.dataset.rmCommunityBound = 'true';
13213    document.addEventListener('click', onClick);
13214    document.addEventListener('keydown', onKeydown);
13215  }
13216
13217  document.addEventListener('DOMContentLoaded', bindOnce);
13218  document.addEventListener('turbolinks:load', bindOnce);
13219  bindOnce();
13220  window.RmCommunity = true;
13221})();
13222// RmSaveToggle: the shared save / unsave (bookmark) button for a race.
13223// Extracted verbatim from search_rebuild.js toggleSave so the athlete profile's "Races for you"
13224// can use the same persistence. Real POST/DELETE to searches_fast#save_race / #unsave_race.
13225//
13226//   <button data-save data-race-id="1" data-race-name="..." data-logged-in="true"><i class="fa-regular fa-bookmark"></i></button>
13227//
13228//   RmSaveToggle.toggle(btn, onAuth)
13229//     onAuth(btn) runs when the visitor is logged out, or the server answers 401: the caller opens its
13230//     own sign-in prompt (search passes AuthModal with intendedAction 'save').
13231// Optimistic toggle, revert on failure.
13232(function () {
13233  'use strict';
13234  if (window.RmSaveToggle) return;
13235
13236  function csrfToken() {
13237    var m = document.querySelector('meta[name="csrf-token"]');
13238    return m ? m.getAttribute('content') : '';
13239  }
13240
13241  function toggle(btn, onAuth) {
13242    function needAuth() { if (typeof onAuth === 'function') onAuth(btn); }
13243    if (btn.dataset.loggedIn !== 'true') {
13244      needAuth();
13245      return;
13246    }
13247    var wasOn = btn.classList.contains('on');
13248    var icon = btn.querySelector('i');
13249    function paint(on) { btn.classList.toggle('on', on); if (icon) icon.className = (on ? 'fa-solid' : 'fa-regular') + ' fa-bookmark'; }
13250    paint(!wasOn); // optimistic
13251    btn.disabled = true;
13252    fetch(wasOn ? '/searches_fast/unsave_race' : '/searches_fast/save_race', {
13253      method: wasOn ? 'DELETE' : 'POST',
13254      credentials: 'same-origin',
13255      headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
13256      body: 'race_occurence_id=' + encodeURIComponent(btn.dataset.raceId) + '&authenticity_token=' 
13256+ encodeURIComponent(csrfToken())
13257    }).then(function (r) {
13258      if (r.status === 401) { paint(wasOn); needAuth(); return null; }
13259      if (!r.ok) { console.error('save toggle failed: HTTP ' + r.status); paint(wasOn); return null; } // any non-OK = not persisted
13260      return r.json().catch(function () { return null; });
13261    }).then(function (data) {
13262      btn.disabled = false;
13263      if (data && data.success === false) paint(wasOn); // server refused -> revert
13264    }).catch(function (e) { btn.disabled = false; paint(wasOn); console.error('save toggle failed:', e); });
13265  }
13266
13267  window.RmSaveToggle = { toggle: toggle };
13268})();
13269/**
13270 * UI Helpers
13271 * Reusable UI utility functions for any page
13272 */
13273
13274
13275(function() {
13276  'use strict';
13277  
13278  // Prevent redeclaration
13279  if (window.UIHelpers) {
13280    return;
13281  }
13282
13283class UIHelpers {
13284  static init() {
13285    this.createNotificationContainer();
13286    this.setupGlobalCSRFToken();
13287  }
13288  
13289  static createNotificationContainer() {
13290    if (!document.getElementById('globalNotificationContainer')) {
13291      const container = document.createElement('div');
13292      container.id = 'globalNotificationContainer';
13293      container.className = 'notification-container';
13294      document.body.appendChild(container);
13295    }
13296  }
13297  
13298  static setupGlobalCSRFToken() {
13299    if (!window.csrfToken) {
13300      const token = document.querySelector('meta[name="csrf-token"]');
13301      if (token) {
13302        window.csrfToken = token.content;
13303      }
13304    }
13305  }
13306  
13307  // Notification System
13308  static showNotification(type, title, message, duration = 5000) {
13309    const container = document.getElementById('globalNotificationContainer');
13310    if (!container) {
13311
13312      return;
13313    }
13314    
13315    const notification = document.createElement('div');
13316    notification.className = `notification notification-${type}`;
13317    notification.innerHTML = `
13318      <div class="notification-content">
13319        <h4 class="notification-title">${title}</h4>
13320        <p class="notification-message">${message}</p>
13321      </div>
13322      <button class="notification-close" onclick="this.parentElement.remove()">&times;</button>
13323    `;
13324    
13325    container.appendChild(notification);
13326    
13327    // Add animation
13328    setTimeout(() => notification.classList.add('show'), 10);
13329    
13330    // Auto-remove after duration
13331    if (duration > 0) {
13332      setTimeout(() => {
13333        if (notification.parentElement) {
13334          notification.classList.add('hide');
13335          setTimeout(() => notification.remove(), 300);
13336        }
13337      }, duration);
13338    }
13339    
13340    return notification;
13341  }
13342  
13343  static removeNotification(notification) {
13344    if (notification && notification.parentElement) {
13345      notification.classList.add('hide');
13346      setTimeout(() => notification.remove(), 300);
13347    }
13348  }
13349  
13350  // Toast System (simpler notifications)
13351  static showToast(message, type = 'info', duration = 3000) {
13352    return this.showNotification(type, '', message, duration);
13353  }
13354
13355  // Alias for fitness_tracker callers (resume_modal, enrollment_wizard, etc.)
13356  // that expect window.UIHelpers.toast() — fitness_tracker/ui_helpers.js
13357  // exposes this name. Without the alias, plan detail/catalog pages (which
13358  // load the app bundle's class-based UIHelpers, not the object literal)
13359  // silently drop those toasts.
13360  static toast(message, type = 'info', duration = 3000) {
13361    return this.showToast(message, type, duration);
13362  }
13363  
13364  // Confirmation Dialog System
13365  static showConfirmation(options = {}) {
13366    const {
13367      title = 'Confirm Action',
13368      message = 'Are you sure?',
13369      confirmText = 'Confirm',
13370      cancelText = 'Cancel',
13371      onConfirm = () => {},
13372      onCancel = () => {},
13373      type = 'warning'
13374    } = options;
13375    
13376    const confirmationId = 'globalConfirmationDialog';
13377    
13378    // Remove existing confirmation
13379    const existing = document.getElementById(confirmationId);
13380    if (existing) {
13381      existing.remove();
13382    }
13383    
13384    const dialog = document.createElement('div');
13385    dialog.id = confirmationId;
13386    dialog.className = 'confirmation-dialog';
13387    
13388    // Add inline styles to ensure visibility
13389    dialog.style.cssText = `
13390      position: fixed;
13391      top: 0;
13392      left: 0;
13393      width: 100%;
13394      height: 100%;
13395      z-index: 999999;
13396      display: flex;
13397      align-items: center;
13398      justify-content: center;
13399    `;
13400    
13401    dialog.innerHTML = `
13402      <div class="confirmation-overlay" style="
13403        position: absolute;
13404        top: 0;
13405        left: 0;
13406        width: 100%;
13407        height: 100%;
13408        background: rgba(0, 0, 0, 0.5);
13409        z-index: 1;
13410      " onclick="UIHelpers.closeConfirmation()"></div>
13411      <div class="confirmation-content" style="
13412        position: relative;
13413        z-index: 2;
13414        background: white;
13415        border-radius: 12px;
13416        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
13417        max-width: 400px;
13418        width: 90%;
13419        padding: 0;
13420        animation: confirmationSlideIn 0.2s ease-out;
13421      ">
13422        <div class="confirmation-header" style="
13423          padding: 24px 24px 0 24px;
13424          text-align: center;
13425        ">
13426          <div class="confirmation-icon confirmation-icon-${type}" style="
13427            font-size: 48px;
13428            margin-bottom: 16px;
13429            ${type === 'danger' ? 'color: #EF4444;' : type === 'warning' ? 'color: #F59E0B;' : 'color: #10B981;'}
13430          ">
13431            ${this.getConfirmationIcon(type)}
13432          </div>
13433          <h3 class="confirmation-title" style="
13434            margin: 0;
13435            font-size: 18px;
13436            font-weight: 600;
13437            color: #111827;
13438            line-height: 1.3;
13439          ">${title}</h3>
13440        </div>
13441        <div class="confirmation-body" style="
13442          padding: 16px 24px;
13443          text-align: center;
13444        ">
13445          <p class="confirmation-message" style="
13446            margin: 0;
13447            font-size: 14px;
13448            color: #6B7280;
13449            line-height: 1.5;
13450          ">${message}</p>
13451        </div>
13452        <div class="confirmation-footer" style="
13453          padding: 0 24px 24px 24px;
13454          display: flex;
13455          gap: 12px;
13456          justify-content: center;
13457        ">
13458          <button type="button" class="confirmation-btn confirmation-cancel" onclick="UIHelpers.closeConfirmation()" style="
13459            flex: 1;
13460            padding: 12px 16px;
13461            background: #F3F4F6;
13462            border: 1px solid #D1D5DB;
13463            border-radius: 8px;
13464            color: #374151;
13465            font-size: 14px;
13466            font-weight: 500;
13467            cursor: pointer;
13468            transition: all 0.2s;
13469          " onmouseover="this.style.background='#E5E7EB'" onmouseout="this.style.background='#F3F4F6'">${cancelText}</button>
13470          <button type="button" class="confirmation-btn confirmation-confirm" onclick="UIHelpers.confirmAction()" style="
13471            flex: 1;
13472            padding: 12px 16px;
13473            background: ${type === 'danger' ? '#EF4444' : 'var(--racemob-primary-orange, #FF6B35)'};
13474            border: 1px solid ${type === 'danger' ? '#DC2626' : 'var(--racemob-primary-orange, #FF6B35)'};
13475            border-radius: 8px;
13476            color: white;
13477            font-size: 14px;
13478            font-weight: 500;
13479            cursor: pointer;
13480            transition: all 0.2s;
13481          " onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">${confirmText}</button>
13482        </div>
13483      </div>
13484    `;
13485    
13486    document.body.appendChild(dialog);
13487    dialog.classList.add('show');
13488    document.body.style.overflow = 'hidden';
13489    
13490    // Store callbacks
13491    this._confirmationCallbacks = { onConfirm, onCancel };
13492    
13493    // Focus confirm button
13494    setTimeout(() => {
13495      const confirmBtn = dialog.querySelector('.confirmation-confirm');
13496      if (confirmBtn) confirmBtn.focus();
13497    }, 100);
13498    
13499    return dialog;
13500  }
13501  
13502  static closeConfirmation() {
13503    const dialog = document.getElementById('globalConfirmationDialog');
13504    if (dialog) {
13505      dialog.remove();
13506      document.body.style.overflow = 'auto';
13507      
13508      if (this._confirmationCallbacks && this._confirmationCallbacks.onCancel) {
13509        this._confirmationCallbacks.onCancel();
13510      }
13511      
13512      this._confirmationCallbacks = null;
13513    }
13514  }
13515  
13516  static confirmAction() {
13517    if (this._confirmationCallbacks && this._confirmationCallbacks.onConfirm) {
13518      this._confirmationCallbacks.onConfirm();
13519    }
13520    
13521    this.closeConfirmation();
13522  }
13523  
13524  static getConfirmationIcon(type) {
13525    const icons = {
13526      warning: '⚠️',
13527      danger: '❌',
13528      info: 'ℹ️',
13529      question: '❓'
13530    };
13531    
13532    return icons[type] || icons.question;
13533  }
13534  
13535  // Loading States
13536  static showLoading(element, text = 'Loading...') {
13537    if (!element) return;
13538    
13539    element.dataset.originalContent = element.innerHTML;
13540    element.innerHTML = `
13541      <span class="loading-spinner"></span>
13542      <span class="loading-text">${text}</span>
13543    `;
13544    element.classList.add('loading');
13545    element.disabled = true;
13546  }
13547  
13548  static hideLoading(element) {
13549    if (!element) return;
13550    
13551    if (element.dataset.originalContent) {
13552      element.innerHTML = element.dataset.originalContent;
13553      delete element.dataset.originalContent;
13554    }
13555    
13556    element.classList.remove('loading');
13557    element.disabled = false;
13558  }
13559  
13560  // Modal Utilities
13561  static createModal(id, title, content, options = {}) {
13562    const {
13563      size = 'medium',
13564      showClose = true,
13565      onClose = () => {}
13566    } = options;
13567    
13568    // Remove existing modal with same ID
13569    const existing = document.getElementById(id);
13570    if (existing) {
13571      existing.remove();
13572    }
13573    
13574    const modal = document.createElement('div');
13575    modal.id = id;
13576    modal.className = `ui-modal ui-modal-${size}`;
13577    
13578    modal.innerHTML = `
13579      <div class="ui-modal-overlay" onclick="UIHelpers.closeModal('${id}')"></div>
13580      <div class="ui-modal-content">
13581        <div class="ui-modal-header">
13582          <h3 class="ui-modal-title">${title}</h3>
13583          ${showClose ? `<button class="ui-modal-close" onclick="UIHelpers.closeModal('${id}')">&times;</button>` : ''}
13584        </div>
13585        <div class="ui-modal-body">
13586          ${content}
13587        </div>
13588      </div>
13589    `;
13590    
13591    document.body.appendChild(modal);
13592    modal.classList.add('show');
13593    document.body.style.overflow = 'hidden';
13594    
13595    // Store close callback
13596    modal._closeCallback = onClose;
13597    
13598    return modal;
13599  }
13600  
13601  static closeModal(id) {
13602    const modal = document.getElementById(id);
13603    if (modal) {
13604      if (modal._closeCallback) {
13605        modal._closeCallback();
13606      }
13607      
13608      modal.remove();
13609      document.body.style.overflow = 'auto';
13610    }
13611  }
13612  
13613  // Form Utilities
13614  static serializeForm(form) {
13615    const formData = new FormData(form);
13616    const data = {};
13617    
13618    for (let [key, value] of formData.entries()) {
13619      if (data[key]) {
13620        if (Array.isArray(data[key])) {
13621          data[key].push(value);
13622        } else {
13623          data[key] = [data[key], value];
13624        }
13625      } else {
13626        data[key] = value;
13627      }
13628    }
13629    
13630    return data;
13631  }
13632  
13633  static validateForm(form) {
13634    const requiredFields = form.querySelectorAll('[required]');
13635    const errors = [];
13636    
13637    requiredFields.forEach(field => {
13638      if (!field.value.trim()) {
13639        errors.push(`${field.name || field.id} is required`);
13640        field.classList.add('error');
13641      } else {
13642        field.classList.remove('error');
13643      }
13644    });
13645    
13646    return errors;
13647  }
13648  
13649  // Animation Utilities
13650  static fadeIn(element, duration = 300) {
13651    element.style.opacity = '0';
13652    element.style.display = 'block';
13653    
13654    const start = performance.now();
13655    
13656    function animate(currentTime) {
13657      const elapsed = currentTime - start;
13658      const progress = Math.min(elapsed / duration, 1);
13659      
13660      element.style.opacity = progress;
13661      
13662      if (progress < 1) {
13663        requestAnimationFrame(animate);
13664      }
13665    }
13666    
13667    requestAnimationFrame(animate);
13668  }
13669  
13670  static fadeOut(element, duration = 300) {
13671    const start = performance.now();
13672    const initialOpacity = parseFloat(getComputedStyle(element).opacity);
13673    
13674    function animate(currentTime) {
13675      const elapsed = currentTime - start;
13676      const progress = Math.min(elapsed / duration, 1);
13677      
13678      element.style.opacity = initialOpacity * (1 - progress);
13679      
13680      if (progress < 1) {
13681        requestAnimationFrame(animate);
13682      } else {
13683        element.style.display = 'none';
13684      }
13685    }
13686    
13687    requestAnimationFrame(animate);
13688  }
13689  
13690  static slideDown(element, duration = 300) {
13691    element.style.height = '0';
13692    element.style.overflow = 'hidden';
13693    element.style.display = 'block';
13694    
13695    const targetHeight = element.scrollHeight + 'px';
13696    
13697    const start = performance.now();
13698    
13699    function animate(currentTime) {
13700      const elapsed = currentTime - start;
13701      const progress = Math.min(elapsed / duration, 1);
13702      
13703      element.style.height = (parseFloat(targetHeight) * progress) + 'px';
13704      
13705      if (progress < 1) {
13706        requestAnimationFrame(animate);
13707      } else {
13708        element.style.height = 'auto';
13709        element.style.overflow = 'visible';
13710      }
13711    }
13712    
13713    requestAnimationFrame(animate);
13714  }
13715  
13716  static slideUp(element, duration = 300) {
13717    const initialHeight = element.offsetHeight;
13718    element.style.height = initialHeight + 'px';
13719    element.style.overflow = 'hidden';
13720    
13721    const start = performance.now();
13722    
13723    function animate(currentTime) {
13724      const elapsed = currentTime - start;
13725      const progress = Math.min(elapsed / duration, 1);
13726      
13727      element.style.height = (initialHeight * (1 - progress)) + 'px';
13728      
13729      if (progress < 1) {
13730        requestAnimationFrame(animate);
13731      } else {
13732        element.style.display = 'none';
13733        element.style.height = 'auto';
13734        element.style.overflow = 'visible';
13735      }
13736    }
13737    
13738    requestAnimationFrame(animate);
13739  }
13740  
13741  // Utility Functions
13742  static debounce(func, wait) {
13743    let timeout;
13744    return function executedFunction(...args) {
13745      const later = () => {
13746        clearTimeout(timeout);
13747        func(...args);
13748      };
13749      clearTimeout(timeout);
13750      timeout = setTimeout(later, wait);
13751    };
13752  }
13753  
13754  static throttle(func, limit) {
13755    let inThrottle;
13756    return function() {
13757      const args = arguments;
13758      const context = this;
13759      if (!inThrottle) {
13760        func.apply(context, args);
13761        inThrottle = true;
13762        setTimeout(() => inThrottle = false, limit);
13763      }
13764    };
13765  }
13766  
13767  static formatFileSize(bytes) {
13768    if (bytes === 0) return '0 Bytes';
13769    const k = 1024;
13770    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
13771    const i = Math.floor(Math.log(bytes) / Math.log(k));
13772    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
13773  }
13774  
13775  static escapeHtml(text) {
13776    const div = document.createElement('div');
13777    div.textContent = text;
13778    return div.innerHTML;
13779  }
13780  
13781  static unescapeHtml(html) {
13782    const div = document.createElement('div');
13783    div.innerHTML = html;
13784    return div.textContent || div.innerText || '';
13785  }
13786}
13787
13788// Initialize UI helpers when DOM is ready
13789if (document.readyState === 'loading') {
13790  document.addEventListener('DOMContentLoaded', () => UIHelpers.init());
13791} else {
13792  UIHelpers.init();
13793}
13794
13795// Export for global use
13796window.UIHelpers = UIHelpers;
13797
13798// Export for module systems
13799if (typeof module !== 'undefined' && module.exports) {
13800  module.exports = UIHelpers;
13801}
13802
13803})(); // End IIFE
13804;
13805// ============================================================================
13806// RaceMob client-side error reporter
13807//
13808// Thin wrapper over the Sentry JS Loader (see app/views/shared/_sentry.html.erb).
13809// Call from .catch handlers to forward errors with structured context.
13810//
13811// Production: forwards to window.Sentry (Loader queues calls until SDK loads).
13812// Dev/test:   no Sentry is loaded — calls fall through to console only.
13813//
13814// Usage:
13815//   window.RaceMobErrors.report('meetup_photo_upload', err, {
13816//     meetup_id: 67, stage: 'create_media', file: 'IMG_1234.jpg'
13817//   });
13818//
13819//   window.RaceMobErrors.message('Server rejected upload', 'warning', {
13820//     team_id: 1, errors: data.errors
13821//   });
13822// ============================================================================
13823
13824(function() {
13825  'use strict';
13826
13827  function hasSentry() {
13828    return typeof window.Sentry !== 'undefined' &&
13829           typeof window.Sentry.captureException === 'function';
13830  }
13831
13832  function toError(label, value) {
13833    if (value instanceof Error) return value;
13834    var msg = (value && value.message) || (typeof value === 'string' ? value : '');
13835    return new Error(label + (msg ? ': ' + msg : ''));
13836  }
13837
13838  function report(label, error, context) {
13839    context = context || {};
13840    var payload = { tags: { error_label: label }, extra: context };
13841
13842    try {
13843      if (hasSentry()) {
13844        window.Sentry.captureException(toError(label, error), payload);
13845      }
13846    } catch (_) { /* never break the page from the reporter itself */ }
13847
13848    if (window.console && console.error) {
13849      console.error('[RaceMobErrors] ' + label, error, context);
13850    }
13851  }
13852
13853  function message(msg, level, context) {
13854    context = context || {};
13855    var resolvedLevel = level || 'warning';
13856    var payload = { level: resolvedLevel, extra: context };
13857
13858    try {
13859      if (hasSentry() && typeof window.Sentry.captureMessage === 'function') {
13860        window.Sentry.captureMessage(msg, payload);
13861      }
13862    } catch (_) {}
13863
13864    if (window.console) {
13865      var logFn = (resolvedLevel === 'info' || resolvedLevel === 'debug')
13866        ? (console.info || console.log)
13867        : (console.warn || console.log);
13868      if (logFn) logFn.call(console, '[RaceMobErrors] ' + msg, context);
13869    }
13870  }
13871
13872  window.RaceMobErrors = { report: report, message: message };
13873})();
13874/**
13875 * Authentication Utilities
13876 *
13877 * Shared authentication logic used by auth_modal.js and enhanced_registration.js
13878 * Extracted to eliminate code duplication and provide a single source of truth for auth operations.
13879 *
13880 * Security Note: This code is client-side and does not handle security validation.
13881 * All security enforcement (CSRF, rate limiting, validation) happens on the backend.
13882 *
13883 * @version 1.0.0
13884 * @author RaceMob Team
13885 */
13886
13887
13888window.AuthUtils = {
13889
13890  /**
13891   * Load Cloudflare Turnstile script if not already loaded
13892   *
13893   * @returns {Promise<boolean>} Whether Turnstile is ready
13894   */
13895  async loadTurnstile() {
13896    // Already loaded
13897    if (window.turnstile && window.turnstile.render) {
13898      return true;
13899    }
13900
13901    // Load the script
13902    return new Promise((resolve) => {
13903      // Check if script is already being loaded
13904      if (document.querySelector('script[src*="turnstile"]')) {
13905        const checkReady = setInterval(() => {
13906          if (window.turnstile && window.turnstile.render) {
13907            clearInterval(checkReady);
13908            resolve(true);
13909          }
13910        }, 100);
13911        setTimeout(() => {
13912          clearInterval(checkReady);
13913          resolve(false);
13914        }, 5000);
13915        return;
13916      }
13917
13918      var script = document.createElement('script');
13919      script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
13920      script.async = true;
13921      script.onload = function() {
13922        console.log('AuthUtils: Turnstile loaded');
13923        resolve(true);
13924      };
13925      script.onerror = function() {
13926        console.error('AuthUtils: Failed to load Turnstile');
13927        resolve(false);
13928      };
13929      document.head.appendChild(script);
13930    });
13931  },
13932
13933  /**
13934   * Get Turnstile token for bot protection
13935   * Returns null if Turnstile is not configured or fails
13936   *
13937   * @param {number} [timeoutMs=10000] - How long to wait for a token before
13938   *   resolving null. Callers whose backend fails OPEN on a missing token
13939   *   (e.g. the newsletter form) can pass a shorter grace so users aren't
13940   *   stuck waiting behind an adblocker that's blocking the Turnstile script.
13941   * @returns {Promise<string|null>} Turnstile token or null
13942   */
13943  async getTurnstileToken(timeoutMs) {
13944    try {
13945      var siteKeyMeta = document.querySelector('meta[name="turnstile-site-key"]');
13946      if (!siteKeyMeta || !siteKeyMeta.content) {
13947        console.log('AuthUtils: Turnstile not configured, skipping');
13948        return null;
13949      }
13950
13951      var siteKey = siteKeyMeta.content;
13952
13953      // Load Turnstile if not already loaded
13954      if (!window.turnstile || !window.turnstile.render) {
13955        var loaded = await this.loadTurnstile();
13956        if (!loaded) {
13957          console.warn('AuthUtils: Failed to load Turnstile, skipping');
13958          return null;
13959        }
13960      }
13961
13962      // Create hidden container for widget if needed
13963      var container = document.getElementById('turnstile-container');
13964      if (!container) {
13965        container = document.createElement('div');
13966        container.id = 'turnstile-container';
13967        container.style.display = 'none';
13968        document.body.appendChild(container);
13969      }
13970
13971      // Remove previous widget if exists
13972      container.innerHTML = '';
13973
13974      // Render widget and get token via callback
13975      return new Promise(function(resolve) {
13976        var timeout = setTimeout(function() {
13977          console.warn('AuthUtils: Turnstile token timeout');
13978          resolve(null);
13979        }, timeoutMs || 10000);
13980
13981        window.turnstile.render(container, {
13982          sitekey: siteKey,
13983          callback: function(token) {
13984            clearTimeout(timeout);
13985            resolve(token);
13986          },
13987          'error-callback': function(code) {
13988            clearTimeout(timeout);
13989            // Surface the Cloudflare error code (e.g. 110200 = sitekey domain
13990            // not allowed — the signature of the Feb-Jun 2026 site-wide signup
13991            // outage). Push to dataLayer so client-side Turnstile failures are
13992            // countable in GTM/GA, not just visible in individual consoles.
13993            console.error('AuthUtils: Turnstile error', code);
13994            try {
13995              (window.dataLayer = window.dataLayer || []).push({
13996                event: 'turnstile_error',
13997                turnstile_error_code: String(code)
13998              });
13999            } catch (e) { /* telemetry must never block auth */ }
14000            resolve(null);
14001          }
14002        });
14003      });
14004    } catch (error) {
14005      console.error('AuthUtils: Turnstile error', error);
14006      return null;
14007    }
14008  },
14009
14010  /**
14011   * Quick signup with email only (auto-generated password on backend)
14012   * Used for inline authentication in modals without requiring password upfront
14013   *
14014   * @param {string} email - User's email address
14015   * @param {Object} options - Optional configuration
14016   * @param {boolean} options.sendVerificationEmail - Whether to send verification email immediately (default: true)
14017   * @returns {Promise<Object>} Result object with success, user data, or error info
14018   *
14019   * Success response: { success: true, logged_in: true, user: { id, email, name, location } }
14020   * Email exists: { success: false, error: string, email_exists: true, oauth_provider: string }
14021   * Error: { success: false, error: string }
14022   */
14023  async quickSignup(email, options = {}) {
14024    const sendVerificationEmail = options.sendVerificationEmail !== false;  // Default true
14025    // Validate email format
14026    if (!this.validateEmail(email)) {
14027      return {
14028        success: false,
14029        error: 'Please enter a valid email address',
14030        field: 'email'
14031      };
14032    }
14033
14034    try {
14035      // Get Turnstile token for bot protection
14036      var turnstileToken = await this.getTurnstileToken();
14037
14038      // Get CSRF token
14039      var csrfToken = this.getCSRFToken();
14040
14041      // Build headers
14042      var headers = {
14043        'Content-Type': 'application/json',
14044        'Accept': 'application/json',
14045        'X-Requested-With': 'XMLHttpRequest'
14046      };
14047
14048      if (csrfToken) {
14049        headers['X-CSRF-Token'] = csrfToken;
14050      }
14051
14052      // Call backend signup endpoint
14053      var response = await fetch('/onboarding/save_email', {
14054        method: 'POST',
14055        headers: headers,
14056        credentials: 'same-origin',  // CRITICAL: Allow cookies to be set/sent
14057        body: JSON.stringify({
14058          email: email,
14059          website: '',  // Honeypot field - must be empty for legitimate users
14060          send_verification_email: sendVerificationEmail,  // Control email sending
14061          turnstile_token: turnstileToken  // Bot protection
14062        })
14063      });
14064
14065      // Parse JSON response
14066      const data = await response.json();
14067
14068      // Handle error responses
14069      if (!response.ok) {
14070        return {
14071          success: false,
14072          error: data.error || data.errors || `Server error: ${response.status}`,
14073          email_exists: data.email_exists || false,
14074          oauth_provider: data.oauth_provider || null,
14075          field: data.field || null,
14076          recovery_action: data.recovery_action || null,
14077          ...data  // Include all backend data
14078        };
14079      }
14080
14081      // Success - user created and logged in.
14082      //
14083      // GA4 conversion "Signup Complete". This is the single chokepoint for every
14084      // JS-driven account creation — the auth modal, the race registration
14085      // wizard's auth step, team onboarding, auth_form_handler and
14086      // enhanced_registration all land here — so one push covers all of them and
14087      // none of them can double-count.
14088      //
14089      // Keyed off `created`, NOT `success`. /onboarding/save_email answers
14090      // `success: true` from three destinations (concerns/login.rb): the returning
14091      // user, the rapid-duplicate idempotency branch, and the real creation. Only
14092      // the last sets `created: true`. The other two are password-gated and
14093      // quickSignup never sends a password, so `success` would be correct today by
14094      // accident — a conversion metric should not rest on an unrelated guard.
14095      //
14096      // The native /onboarding/start POST and the OAuth callback navigate away
14097      // instead of resolving a promise; those two push from the server via
14098      // session[:signup_complete] (shared/_gtm.html.erb), and the JSON branch
14099      // deliberately does not set that key, so exactly one Signup-Complete exists
14100      // per account.
14101      if (data && data.created === true && window.RmTrack) {
14102        window.RmTrack.signup('Signup-Complete', {
14103          signup_source: options.source || 'quick_signup'
14104        });
14105      }
14106
14107      return data;
14108
14109    } catch (error) {
14110      console.error('Quick signup error:', error);
14111      return {
14112        success: false,
14113        error: 'Network error. Please check your connection and try again.'
14114      };
14115    }
14116  },
14117
14118  /**
14119   * Sign in with email and password
14120   *
14121   * @param {string} email - User's email address
14122   * @param {string} password - User's password
14123   * @param {boolean} rememberMe - Whether to persist login (optional)
14124   * @returns {Promise<Object>} Result object with success/error
14125   *
14126   * Success: { success: true, user: { email }, redirect_to: string }
14127   * OAuth hint: { success: false, oauth_provider: string }
14128   * Error: throws Error
14129   */
14130  async signIn(email, password, rememberMe = false, options = {}) {
14131    try {
14132      // Create FormData (Rails SessionsController expects form data)
14133      const formData = new FormData();
14134      formData.append('login', email);  // SessionsController expects 'login' param
14135      formData.append('password', password);
14136      formData.append('remember_me', rememberMe);
14137      // Modal logins with a pending intended action tell the server to SKIP
14138      // the gate-stored deeplink return (AUTH_DEEPLINK_FLOW.md) — otherwise
14139      // the server's redirect_to would run before the action and eat it.
14140      // Send the real action name (not a bare '1' flag) so the server can
14141      // tell a real queued action apart from a plain "Log In" click.
14142      if (options.intendedAction) {
14143        formData.append('intended_action', options.intendedAction);
14144      }
14145
14146      // Call backend login endpoint
14147      const response = await fetch('/login', {
14148        method: 'POST',
14149        headers: {
14150          'X-CSRF-Token': this.getCSRFToken(),
14151          'X-Requested-With': 'XMLHttpRequest',
14152          'Accept': 'application/json, text/javascript, */*; q=0.01'
14153        },
14154        credentials: 'same-origin',  // Include cookies in request and accept Set-Cookie in response
14155        body: formData
14156      });
14157
14158      if (response.ok) {
14159        // SessionsController#finish_login uses `format.js { render json: ... }`,
14160        // which Rails serves with `Content-Type: text/javascript` — not
14161        // `application/json`. The body is still JSON. Accept either content
14162        // type, then introspect the parsed body to decide success / failure.
14163        const contentType = response.headers.get('content-type') || '';
14164        const looksLikeJsonBody = contentType.includes('json') || contentType.includes('javascript');
14165
14166        if (!looksLikeJsonBody) {
14167          // No structured body — treat as opaque success and let the page
14168          // reload pick up the authenticated session.
14169          return { success: true, user: { email: email } };
14170        }
14171
14172        let result;
14173        try {
14174          result = await response.json();
14175        } catch (parseError) {
14176          return { success: true, user: { email: email } };
14177        }
14178
14179        // PR #289 envelope: SessionsController now stamps `success: true|false`
14180        // on every JSON response. Prefer that field; legacy keys remain as
14181        // back-compat for any non-migrated endpoint (OnboardingController,
14182        // RegistrationsController, /api/v1/*).
14183        //
14184        // Pass through any domain-specific keys the server stamps alongside
14185        // the envelope (e.g. widget_token in the embeddable widget flow at
14186        // sessions_controller.rb:203). Spreading `result` first preserves
14187        // those keys; the explicit overrides assert shape afterwards.
14188        if (typeof result.success === 'boolean') {
14189          if (result.success) {
14190            return {
14191              ...result,
14192              success: true,
14193              user: { email: email },
14194              redirect_to: result.redirect_to
14195            };
14196          }
14197          return {
14198            ...result,
14199            success: false,
14200            error: result.error || 'Invalid email or password',
14201            oauth_provider: result.oauth_provider || null,
14202            email: result.email || email
14203          };
14204        }
14205
14206        // Legacy fallback — pre-PR #289 envelope shapes.
14207        if (result.redirect_to) {
14208          return {
14209            success: true,
14210            user: { email: email },
14211            redirect_to: result.redirect_to
14212          };
14213        }
14214
14215        if (result.oauth_provider) {
14216          return {
14217            success: false,
14218            oauth_provider: result.oauth_provider,
14219            email: result.email || email,
14220            error: 'Invalid password'
14221          };
14222        }
14223
14224        // Legacy `{html: ...}` failure envelope (try_login_again JS branch
14225        // pre-PR #289). No redirect_to, no oauth_provider. Surface as a
14226        // wrong-credentials failure that the modal can render inline.
14227        if (result.html !== undefined) {
14228          return {
14229            success: false,
14230            error: 'Invalid username or password',
14231            email: email
14232          };
14233        }
14234      }
14235
14236      // Login failed
14237      throw new Error('Invalid email or password');
14238
14239    } catch (error) {
14240      console.error('Sign in error:', error);
14241      throw error;
14242    }
14243  },
14244
14245  /**
14246   * Update page authentication state without full reload
14247   * Fetches updated header HTML and replaces navigation
14248   *
14249   * @param {Object} user - User data object
14250   * @returns {Promise<void>}
14251   */
14252  async updatePageAuthState(user) {
14253    try {
14254      // Fetch updated header HTML with credentials to send auth cookies
14255      const response = await fetch('/auth/user_header', {
14256        credentials: 'same-origin'  // CRITICAL: Send persistence token cookie
14257      });
14258      const headerHTML = await response.text();
14259
14260      // Try to update .auth-section element (contains user menu in nav)
14261      const authSection = document.querySelector('.auth-section');
14262      if (authSection) {
14263        authSection.innerHTML = headerHTML;
14264      } else if (document.querySelector('header')) {
14265        // Fallback to header element
14266        document.querySelector('header').innerHTML = headerHTML;
14267      } else {
14268        // Last resort: full page reload
14269        console.warn('Could not find .auth-section or header element, reloading page');
14270        window.location.reload();
14271        return;
14272      }
14273
14274      // Update body data attribute for other scripts
14275      document.body.dataset.userLoggedIn = 'true';
14276
14277      // Dispatch custom event for other scripts to listen to
14278      document.dispatchEvent(new CustomEvent('auth:login', { detail: user }));
14279
14280      console.log('Page auth state updated successfully');
14281    } catch (error) {
14282      console.error('Failed to update page auth state:', error);
14283      // Fallback to page reload on error
14284      window.location.reload();
14285    }
14286  },
14287
14288  /**
14289   * Validate email format
14290   * Note: This is client-side validation only (for UX). Backend always validates.
14291   *
14292   * @param {string} email - Email address to validate
14293   * @returns {boolean} Whether email format is valid
14294   */
14295  validateEmail(email) {
14296    if (!email || typeof email !== 'string') {
14297      return false;
14298    }
14299    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14300    return emailRegex.test(email);
14301  },
14302
14303  /**
14304   * Get CSRF token from meta tag
14305   * Rails puts CSRF token in meta tag for AJAX requests
14306   *
14307   * @returns {string} CSRF token or empty string if not found
14308   */
14309  getCSRFToken() {
14310    const token = document.querySelector('meta[name="csrf-token"]');
14311    return token ? token.getAttribute('content') : '';
14312  },
14313
14314  /**
14315   * Get request headers with CSRF token
14316   *
14317   * @param {string} contentType - Content-Type header value (default: 'application/json')
14318   * @returns {Object} Headers object
14319   */
14320  getHeaders(contentType = 'application/json') {
14321    const headers = {
14322      'X-CSRF-Token': this.getCSRFToken(),
14323      'X-Requested-With': 'XMLHttpRequest',
14324      'Accept': 'application/json'
14325    };
14326
14327    if (contentType) {
14328      headers['Content-Type'] = contentType;
14329    }
14330
14331    return headers;
14332  },
14333
14334  /**
14335   * Get friendly OAuth provider name for display
14336   *
14337   * @param {string} provider - OAuth provider identifier (google_oauth2, facebook, etc.)
14338   * @returns {string} Friendly provider name
14339   */
14340  getProviderName(provider) {
14341    const names = {
14342      'google_oauth2': 'Google',
14343      'facebook': 'Facebook'
14344    };
14345    return names[provider] || provider;
14346  },
14347
14348  /**
14349   * Wait for cookies to sync after authentication
14350   * Backend sets session cookie, but browser needs time to process it
14351   * This prevents 401 errors from race conditions
14352   *
14353   * @param {Function} callback - Function to call after delay
14354   * @param {number} delayMs - Delay in milliseconds (default: 150ms)
14355   */
14356  waitForCookieSync(callback, delayMs = 150) {
14357    setTimeout(callback, delayMs);
14358  },
14359
14360  /**
14361   * Send verification email to user
14362   * Called after signup or when user requests resend
14363   *
14364   * @returns {Promise<Object>} Result object
14365   */
14366  async sendVerificationEmail() {
14367    try {
14368      const response = await fetch('/user/send_verification_email', {
14369        method: 'POST',
14370        headers: {
14371          'X-CSRF-Token': this.getCSRFToken()
14372        },
14373        credentials: 'same-origin'
14374      });
14375
14376      const result = await response.json();
14377      return result;
14378    } catch (error) {
14379      console.error('Failed to send verification email:', error);
14380      return { success: false, error: error.message };
14381    }
14382  },
14383
14384  /**
14385   * Update user profile after quick signup
14386   * Used when user completes optional profile fields
14387   *
14388   * @param {Object} profileData - Profile data (name, location, password, birthday, gender)
14389   * @returns {Promise<Object>} Result object
14390   */
14391  async updateProfile(profileData) {
14392    try {
14393      const response = await fetch('/onboarding/update_profile', {
14394        method: 'POST',
14395        headers: this.getHeaders(),
14396        credentials: 'same-origin',
14397        body: JSON.stringify(profileData)
14398      });
14399
14400      const result = await response.json();
14401      return result;
14402    } catch (error) {
14403      console.error('Profile update error:', error);
14404      return { success: false, error: error.message };
14405    }
14406  }
14407};
14408
14409// Export for module systems (if needed)
14410if (typeof module !== 'undefined' && module.exports) {
14411  module.exports = window.AuthUtils;
14412}
14413;
14414/**
14415 * AuthFormHandler
14416 *
14417 * Declarative submit handler for any auth form rendered by the
14418 * shared/_auth_form_fields.html.erb partial (or any form that opts
14419 * in via the same data-auth-form attribute).
14420 *
14421 * Attaches by data attribute, never by id, so multiple forms on the
14422 * same page work independently:
14423 *
14424 *   <form data-auth-form="signin"  data-submit-url="/login">      → AuthUtils.signIn
14425 *   <form data-auth-form="signup"  data-submit-url="/signup">      → AuthUtils.quickSignup
14426 *   <form data-auth-form="forgot"  data-submit-url="/passwords">   → fetch JSON
14427 *
14428 * Response envelope (PR #289 / standardized):
14429 *   { success: true,  redirect_to?: string }
14430 *   { success: false, error: string, oauth_provider?: string }
14431 *
14432 * Legacy fallback (for endpoints PR2 marked audit-only:
14433 * OnboardingController, RegistrationsController, /api/v1/*) is
14434 * handled inside AuthUtils.signIn / AuthUtils.quickSignup themselves.
14435 *
14436 * Idempotent — calling bindAll() repeatedly is safe (per-form guard).
14437 *
14438 * @version 1.0.0
14439 */
14440
14441(function () {
14442  if (window.AuthFormHandler) return; // double-load guard
14443
14444  var BOUND_FLAG = '__authFormHandlerBound';
14445  var SUBMITTING_FLAG = '__authFormSubmitting';
14446
14447  function bindAll(root) {
14448    var scope = root || document;
14449    var forms = scope.querySelectorAll('form[data-auth-form]');
14450    for (var i = 0; i < forms.length; i++) {
14451      bind(forms[i]);
14452    }
14453  }
14454
14455  function bind(form) {
14456    if (!form || form[BOUND_FLAG]) return;
14457    form[BOUND_FLAG] = true;
14458    form.addEventListener('submit', handleSubmit);
14459  }
14460
14461  async function handleSubmit(event) {
14462    var form = event.currentTarget;
14463    event.preventDefault();
14464
14465    // Clear stale errors before native validation runs so a previously-failed
14466    // attempt doesn't sit next to a fresh browser-validation tooltip.
14467    clearAllErrors(form);
14468
14469    // Native validity — let the browser bubble per-field tooltips.
14470    if (typeof form.checkValidity === 'function' && !form.checkValidity()) {
14471      if (typeof form.reportValidity === 'function') form.reportValidity();
14472      return;
14473    }
14474
14475    if (form[SUBMITTING_FLAG]) {
14476      // Double-submit guard. Keep silent — user already saw the spinner.
14477      return;
14478    }
14479    form[SUBMITTING_FLAG] = true;
14480
14481    var submitBtn = form.querySelector('[data-auth-form-submit], button[type="submit"], input[type="submit"]');
14482    var originalLabel = setLoadingState(submitBtn);
14483
14484    var mode = form.dataset.authForm;
14485    var data = readFormData(form);
14486
14487    try {
14488      if (!window.AuthUtils) {
14489        // Auth bundle not loaded — fall through to native submit so user
14490        // is not blocked. Releases submitting flag in finally.
14491        form[BOUND_FLAG] = false;
14492        form[SUBMITTING_FLAG] = false;
14493        restoreSubmitButton(submitBtn, originalLabel);
14494        form.submit();
14495        return;
14496      }
14497
14498      if (mode === 'signin') {
14499        await handleSignin(form, data);
14500      } else if (mode === 'signup') {
14501        await handleSignup(form, data);
14502      } else if (mode === 'forgot') {
14503        await handleForgot(form, data);
14504      } else {
14505        console.warn('[AuthFormHandler] Unknown mode:', mode);
14506      }
14507    } catch (err) {
14508      // AuthUtils rejects for true network/parse failures only post-PR2.
14509      console.error('[AuthFormHandler] submit error:', err);
14510      showFormError(form, (err && err.message) || 'Something went wrong. Please try again.');
14511      breadcrumb('exception', { mode: mode, message: err && err.message });
14512    } finally {
14513      form[SUBMITTING_FLAG] = false;
14514      restoreSubmitButton(submitBtn, originalLabel);
14515    }
14516  }
14517
14518  async function handleSignin(form, data) {
14519    var email = data.login || data.email;
14520    var password = data.password;
14521    var rememberMe = data.remember_me === '1' || data.remember_me === 'on';
14522
14523    var result = await window.AuthUtils.signIn(email, password, rememberMe);
14524
14525    if (result && result.success) {
14526      if (result.redirect_to) {
14527        window.location.assign(result.redirect_to);
14528        return;
14529      }
14530      window.location.reload();
14531      return;
14532    }
14533
14534    var message = (result && result.error) || 'Invalid email or password';
14535    if (result && result.oauth_provider) {
14536      var providerName = (window.AuthUtils.getProviderName && window.AuthUtils.getProviderName(result.oauth_provider)) || result.oauth_provider;
14537      message = 'Invalid password. Hint: you previously signed in with ' + providerName + '.';
14538    }
14539    showFormError(form, message);
14540    breadcrumb('signin_failure', { mode: 'signin', oauth_provider: result && result.oauth_provider });
14541    focusFirst(form, 'input[type="password"]');
14542  }
14543
14544  async function handleSignup(form, data) {
14545    var email = data.email || data.login;
14546    var result = await window.AuthUtils.quickSignup(email, { sendVerificationEmail: false, source: 'auth_form' });
14547
14548    if (result && result.success) {
14549      if (result.redirect_to) {
14550        window.location.assign(result.redirect_to);
14551        return;
14552      }
14553      window.location.reload();
14554      return;
14555    }
14556
14557    var message = (result && result.error) || 'Could not create your account. Please try again.';
14558    if (result && result.email_exists) {
14559      message = (result.oauth_provider && window.AuthUtils.getProviderName)
14560        ? 'An account with this email already exists. Sign in with ' + window.AuthUtils.getProviderName(result.oauth_provider) + ' or enter your password.'
14561        : 'An account with this email already exists. Please sign in.';
14562    }
14563    showFormError(form, message);
14564    breadcrumb('signup_failure', { mode: 'signup', email_exists: !!(result && result.email_exists) });
14565  }
14566
14567  async function handleForgot(form, data) {
14568    var email = data.email || data.login;
14569    var submitUrl = form.dataset.submitUrl || '/passwords';
14570
14571    var csrfToken = readCsrfToken();
14572    var response = await fetch(submitUrl, {
14573      method: 'POST',
14574      credentials: 'same-origin',
14575      headers: {
14576        'Content-Type': 'application/json',
14577        'Accept': 'application/json',
14578        'X-CSRF-Token': csrfToken,
14579        'X-Requested-With': 'XMLHttpRequest'
14580      },
14581      body: JSON.stringify({ login: email })
14582    });
14583
14584    if (!response.ok) {
14585      showFormError(form, 'Could not send reset link. Please try again.');
14586      breadcrumb('forgot_failure', { status: response.status });
14587      return;
14588    }
14589
14590    // PasswordsController returns 200 + body whether the email exists or
14591    // not (security: no account enumeration). Surface the same neutral
14592    // success message regardless of response body.
14593    showFormSuccess(form, "If an account exists with that email, we've sent password reset instructions.");
14594  }
14595
14596  // --- DOM helpers (all CSP-safe; textContent only) -----------------------
14597
14598  function readFormData(form) {
14599    var fd = new FormData(form);
14600    var out = {};
14601    fd.forEach(function (value, key) { out[key] = value; });
14602    return out;
14603  }
14604
14605  function clearAllErrors(form) {
14606    var slots = form.querySelectorAll('[data-auth-form-error], [data-auth-form-field-error]');
14607    for (var i = 0; i < slots.length; i++) {
14608      slots[i].textContent = '';
14609      slots[i].hidden = true;
14610      slots[i].removeAttribute('data-auth-form-status');
14611    }
14612  }
14613
14614  function showFormError(form, message) {
14615    var slot = form.querySelector('[data-auth-form-error]');
14616    if (!slot) return;
14617    slot.textContent = message;
14618    slot.hidden = false;
14619    slot.setAttribute('data-auth-form-status', 'error');
14620  }
14621
14622  // Used by mode="forgot" — PasswordsController returns 200 whether or not
14623  // the email exists, so we render the neutral confirmation in the same slot.
14624  // Consumers style on `[data-auth-form-status="success"]` (no class swap).
14625  function showFormSuccess(form, message) {
14626    var slot = form.querySelector('[data-auth-form-error]');
14627    if (!slot) return;
14628    slot.textContent = message;
14629    slot.hidden = false;
14630    slot.setAttribute('data-auth-form-status', 'success');
14631  }
14632
14633  function focusFirst(form, selector) {
14634    var el = form.querySelector(selector);
14635    if (el && typeof el.focus === 'function') el.focus();
14636  }
14637
14638  function setLoadingState(btn) {
14639    if (!btn) return null;
14640    var label = btn.tagName === 'INPUT' ? btn.value : btn.textContent;
14641    btn.dataset.originalLabel = label;
14642    btn.disabled = true;
14643    // Surface-specific loading text via data-auth-form-loading-label;
14644    // fall back to a generic default.
14645    var loadingLabel = btn.dataset.authFormLoadingLabel || 'Working…';
14646    if (btn.tagName === 'INPUT') {
14647      btn.value = loadingLabel;
14648    } else {
14649      btn.textContent = loadingLabel;
14650    }
14651    return label;
14652  }
14653
14654  function restoreSubmitButton(btn, label) {
14655    if (!btn) return;
14656    btn.disabled = false;
14657    var restoreLabel = label || btn.dataset.originalLabel;
14658    if (!restoreLabel) return;
14659    if (btn.tagName === 'INPUT') {
14660      btn.value = restoreLabel;
14661    } else {
14662      btn.textContent = restoreLabel;
14663    }
14664  }
14665
14666  function readCsrfToken() {
14667    var meta = document.querySelector('meta[name="csrf-token"]');
14668    return meta ? meta.getAttribute('content') : '';
14669  }
14670
14671  function breadcrumb(event, data) {
14672    if (window.Sentry && typeof window.Sentry.addBreadcrumb === 'function') {
14673      window.Sentry.addBreadcrumb({
14674        category: 'auth-form-handler',
14675        message: event,
14676        level: 'info',
14677        data: data || {}
14678      });
14679    }
14680  }
14681
14682  // --- public API + auto-bind --------------------------------------------
14683
14684  window.AuthFormHandler = {
14685    bindAll: bindAll,
14686    bind: bind
14687  };
14688
14689  if (document.readyState === 'loading') {
14690    document.addEventListener('DOMContentLoaded', function () { bindAll(); });
14691  } else {
14692    bindAll();
14693  }
14694
14695  // Turbolinks support — re-bind on page transitions. New forms in the DOM
14696  // pick up handlers; previously-bound forms are no-ops (BOUND_FLAG).
14697  if (typeof document.addEventListener === 'function') {
14698    document.addEventListener('turbolinks:load', function () { bindAll(); });
14699  }
14700})();
14701
14702
14703/**
14704 * AuthModal - Contextual Authentication Modal System
14705 *
14706 * A configurable modal component that adapts to different user actions and contexts
14707 * to maximize conversion while maintaining excellent user experience.
14708 *
14709 * @version 1.0.0
14710 * @author RaceMob Team
14711 *
14712 * Wrapped in an IIFE with an idempotency guard. base.js (which `//= require
14713 * auth_modal`s this file) is included by both application.js and
14714 * fitness_tracker.js manifests, so without the guard the bundle assets
14715 * declare `class AuthModal` twice on plan-detail pages and the page-level
14716 * SyntaxError aborts every other top-level script — including
14717 * EnrollmentWizard.bindEvents — leaving "Start This Plan" inert.
14718 */
14719
14720
14721(function () {
14722  if (typeof window !== 'undefined' && window.AuthModal) {
14723    return;
14724  }
14725
14726class AuthModal {
14727  constructor() {
14728
14729    this.config = null;
14730    this.currentStep = 'email';
14731    this.currentAuthTab = 'signup'; // Track whether we're in signup or signin mode
14732    this.userData = {};
14733    this.isOpen = false;
14734    this.isSubmitting = false; // Prevent double-submission
14735    this.modalElement = null;
14736    this.overlayElement = null;
14737
14738    // Post-authentication callback storage
14739    this.intendedAction = null;
14740    this.intendedData = null;
14741
14742    // Track if page needs reload on close (after quick signup)
14743    this.shouldReloadOnClose = false;
14744
14745    // Event tracking
14746    this.sessionId = this.generateSessionId();
14747
14748    // Bind methods to maintain context
14749    this.show = this.show.bind(this);
14750    this.hide = this.hide.bind(this);
14751    this.handleOverlayMouseDown = this.handleOverlayMouseDown.bind(this);
14752    this.handleOverlayMouseUp = this.handleOverlayMouseUp.bind(this);
14753    this.handleOverlayClick = this.handleOverlayClick.bind(this);
14754    this.handleEscapeKey = this.handleEscapeKey.bind(this);
14755    this.handleFormSubmit = this.handleFormSubmit.bind(this);
14756
14757    // Track where mouse events occur on overlay
14758    this.mouseDownOnOverlay = false;
14759    this.mouseUpOnOverlay = false;
14760  }
14761
14762  /**
14763   * Show the modal with given configuration
14764   * @param {Object} config - Modal configuration object
14765   */
14766  show(config = {}) {
14767    this.config = this.validateAndMergeConfig(config);
14768    this.currentStep = 'email';
14769    this.userData = {};
14770
14771    // Store intended action for post-auth execution
14772    this.intendedAction = config.intendedAction || null;
14773    this.intendedData = config.intendedData || null;
14774
14775    // Where to land once the user is authenticated. Callers that opened the modal
14776    // INSTEAD of following a link (components/auth_gate) set this so sign-in ends on
14777    // the page the user actually clicked, rather than reloading the one they left.
14778    this.postAuthUrl = config.postAuthUrl || null;
14779
14780    // Set default tab from config
14781    this.currentAuthTab = config.defaultTab || 'signup';
14782
14783    // GA4 conversion "Signup Start", via RmTrack.signup — a gtag call, not a
14784    // dataLayer push (#723): the container's GA4 tags are paused, and its
14785    // hyphen-to-space rename now lives in the seam. Only when
14786    // the modal opens on the signup tab: opening it to sign in is not a signup
14787    // starting. `signup_source` separates this from the /onboarding/start and
14788    // race-wizard starts, because a single undifferentiated count cannot answer
14789    // which signup path converts.
14790    //
14791    // Held on `this` so the PAIRED Signup-Complete — pushed by
14792    // AuthUtils.quickSignup in handleQuickSignup below — carries the same value.
14793    // Reading config in one place and hardcoding 'auth_modal' in the other put a
14794    // caller's start and complete in different buckets and made per-source
14795    // conversion unjoinable, which is the entire reason the dimension exists.
14796    this.signupSource = config.signupSource || 'auth_modal';
14797    if (this.currentAuthTab === 'signup' && window.RmTrack) {
14798      window.RmTrack.signup('Signup-Start', {
14799        signup_source: this.signupSource,
14800        intent: this.intendedAction || null
14801      });
14802    }
14803
14804    this.createModalElements();
14805
14806    // Set initial title/subtitle based on defaultTab
14807    this.updateTitleForTab(this.currentAuthTab);
14808
14809    this.renderCurrentStep();
14810    this.attachEventListeners();
14811    this.animateIn();
14812
14813    this.isOpen = true;
14814    
14815    // Prevent body scroll
14816    document.body.style.overflow = 'hidden';
14817    
14818    // Focus management
14819    this.trapFocus();
14820    
14821
14822
14823  }
14824
14825  /**
14826   * Land the user after successful auth: on postAuthUrl when the modal stood in for a
14827   * link, otherwise a plain reload of the current page (the long-standing behaviour).
14828   */
14829  finishAuthNavigation() {
14830    if (this.postAuthUrl) {
14831      const target = this.postAuthUrl;
14832      this.postAuthUrl = null;
14833      window.location.assign(target);
14834      return;
14835    }
14836    window.location.reload();
14837  }
14838
14839  /**
14840   * Hide the modal
14841   */
14842  hide() {
14843    if (!this.isOpen) return;
14844
14845    // Check if we need to reload after closing (quick signup flow)
14846    const needsReload = this.shouldReloadOnClose;
14847    const postAuthUrl = this.postAuthUrl;
14848
14849    this.animateOut(() => {
14850      this.cleanup();
14851
14852      // Reload page if quick signup happened (user is logged in)
14853      if (needsReload) {
14854        if (postAuthUrl) {
14855          window.location.assign(postAuthUrl);
14856        } else {
14857          window.location.reload();
14858        }
14859      }
14860    });
14861
14862    this.isOpen = false;
14863
14864    // Restore body scroll
14865    document.body.style.overflow = '';
14866
14867
14868  }
14869
14870  /**
14871   * Validate and merge configuration with defaults
14872   * @param {Object} config - User provided configuration
14873   * @returns {Object} Merged configuration
14874   */
14875  validateAndMergeConfig(config) {
14876    const defaultConfig = {
14877      // Context Bar (optional)
14878      context: null,
14879      
14880      // Main Content
14881      title: 'Create your account',
14882      subtitle: 'Join the running community',
14883      
14884      // Features
14885      benefits: true,
14886      benefitsList: ['Track races', 'Connect with runners', 'Get notifications'],
14887      defaultTab: 'signup', // 'signup' or 'signin'
14888      
14889      // Styling
14890      showProgressDots: true,
14891      
14892      // Callbacks
14893      onSuccess: null,
14894      onCancel: null,
14895      onStepChange: null,
14896      
14897      // Form configuration
14898      fields: {
14899        email: { required: true, label: 'Email' },
14900        name: { required: true, label: 'What should we call you?' },
14901        location: { required: true, label: 'Where do you run?' },
14902        password: { required: true, label: 'Password' },
14903        birthday: { required: false, label: 'Birthday' },
14904        gender: { required: false, label: 'Gender' }
14905      }
14906    };
14907
14908    return Object.assign({}, defaultConfig, config);
14909  }
14910
14911  /**
14912   * Create modal DOM elements
14913   */
14914  createModalElements() {
14915    // Remove existing modal if present
14916    this.cleanup();
14917
14918    // Create overlay - use unique class to avoid conflicts
14919    this.overlayElement = document.createElement('div');
14920    this.overlayElement.className = 'auth-overlay';
14921
14922    // Create modal container - use unique class to avoid conflicts
14923    this.modalElement = document.createElement('div');
14924    this.modalElement.className = 'auth-container';
14925    this.modalElement.innerHTML = this.getModalHTML();
14926
14927    // Append to overlay
14928    this.overlayElement.appendChild(this.modalElement);
14929
14930    // Append to body
14931    document.body.appendChild(this.overlayElement);
14932  }
14933
14934  /**
14935   * Generate modal HTML structure
14936   * @returns {string} Modal HTML
14937   */
14938  getModalHTML() {
14939    return `
14940      <button class="auth-modal-close" aria-label="Close modal">
14941        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
14942          <line x1="18" y1="6" x2="6" y2="18"></line>
14943          <line x1="6" y1="6" x2="18" y2="18"></line>
14944        </svg>
14945      </button>
14946      
14947      ${this.config.context ? this.getContextBarHTML() : ''}
14948      
14949      <div class="auth-modal-header">
14950        <h2 class="auth-modal-title">${this.config.title}</h2>
14951        <p class="auth-modal-subtitle">${this.config.subtitle}</p>
14952      </div>
14953      
14954      ${this.config.showProgressDots ? this.getProgressDotsHTML() : ''}
14955      
14956      <div class="auth-modal-body">
14957        <div class="auth-modal-content">
14958          <!-- Content will be dynamically inserted here -->
14959        </div>
14960      </div>
14961    `;
14962  }
14963
14964  /**
14965   * Generate context bar HTML
14966   * @returns {string} Context bar HTML
14967   */
14968  getContextBarHTML() {
14969    if (!this.config.context) return '';
14970    
14971    return `
14972      <div class="auth-modal-context">
14973        <div class="context-icon">${this.config.context.icon || '🏃'}</div>
14974        <div class="context-info">
14975          <div class="context-title">${this.config.context.title || ''}</div>
14976          <div class="context-subtitle">${this.config.context.subtitle || ''}</div>
14977        </div>
14978      </div>
14979    `;
14980  }
14981
14982  /**
14983   * Generate progress dots HTML
14984   * @returns {string} Progress dots HTML
14985   */
14986  getProgressDotsHTML() {
14987    return `
14988      <div class="auth-modal-progress">
14989        <div class="progress-dot active" data-step="email"></div>
14990        <div class="progress-dot" data-step="profile"></div>
14991      </div>
14992    `;
14993  }
14994
14995  /**
14996   * Render the current step content
14997   */
14998  renderCurrentStep() {
14999    // Reset submission lock when changing steps
15000    this.isSubmitting = false;
15001
15002    const contentContainer = this.modalElement.querySelector('.auth-modal-content');
15003
15004    switch (this.currentStep) {
15005      case 'email':
15006        contentContainer.innerHTML = this.getEmailStepHTML();
15007        // After rendering, check if we need to switch to signin form
15008        if (this.currentAuthTab === 'signin') {
15009          setTimeout(() => this.renderAuthForm(), 0);
15010        }
15011        // Set up "Set Password Now" button
15012        setTimeout(() => {
15013          const fullSignupBtn = this.modalElement.querySelector('#full-signup-btn');
15014          if (fullSignupBtn) {
15015            fullSignupBtn.addEventListener('click', () => {
15016              this.currentStep = 'profile';
15017              this.renderCurrentStep();
15018            });
15019          }
15020        }, 0);
15021        break;
15022      case 'profile':
15023        contentContainer.innerHTML = this.getProfileStepHTML();
15024        break;
15025      case 'success':
15026        contentContainer.innerHTML = this.getSuccessStepHTML();
15027        break;
15028      default:
15029
15030    }
15031
15032    this.updateProgressDots();
15033    this.updateHeaderContent();
15034
15035    // Callback for step change
15036    if (this.config.onStepChange) {
15037      this.config.onStepChange(this.currentStep);
15038    }
15039  }
15040
15041  /**
15042   * Generate email step HTML
15043   * @returns {string} Email step HTML
15044   */
15045  getEmailStepHTML() {
15046    return `
15047      ${this.config.benefits ? this.getBenefitsHTML() : ''}
15048      
15049      <div class="auth-tabs">
15050        <button class="auth-tab ${this.config.defaultTab === 'signup' ? 'active' : ''}" data-tab="signup">
15051          Sign Up
15052        </button>
15053        <button class="auth-tab ${this.config.defaultTab === 'signin' ? 'active' : ''}" data-tab="signin">
15054          Sign In
15055        </button>
15056      </div>
15057      
15058      <div class="social-auth-section">
15059        <div class="social-buttons">
15060          <button class="social-btn google-btn" data-provider="google">
15061            <svg class="social-icon" viewBox="0 0 24 24" width="20" height="20">
15062              <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
15063              <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
15064              <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
15065              <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
15066            </svg>
15067          </button>
15068          <button class="social-btn facebook-btn" data-provider="facebook">
15069            <svg class="social-icon" viewBox="0 0 24 24" width="20" height="20">
15070              <path fill="#1877F2" d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
15071            </svg>
15072          </button>
15073        </div>
15074        
15075        <div class="divider">
15076          <span>or</span>
15077        </div>
15078      </div>
15079
15080      <form class="auth-form" id="email-step-form" method="post" action="/onboarding/save_email">
15081        <div class="form-group">
15082          <label class="form-label" for="auth-email">${this.config.fields.email.label}</label>
15083          <input
15084            type="email"
15085            id="auth-email"
15086            name="email"
15087            class="form-input"
15088            placeholder="[email protected]"
15089            required>
15090          <div class="form-error" id="email-error"></div>
15091        </div>
15092
15093        <!-- Honeypot field for bot protection (hidden from legitimate users) -->
15094        <input type="text" name="website" style="position: absolute; left: -5000px;" tabindex="-1" autocomplete="off" aria-hidden="true">
15095
15096        <p class="helper-text">${this.getContextualHelperText()}</p>
15097
15098        <button type="submit" class="submit-btn primary" data-quick-signup="true">Continue</button>
15099      </form>
15100    `;
15101  }
15102
15103  /**
15104   * Get contextual helper text based on modal context
15105   * @returns {string} Helper text
15106   */
15107  getContextualHelperText() {
15108    if (this.config.context && this.config.context.title) {
15109      // Contextual modal (e.g., "Save Race", "Write Review")
15110      return `We'll email you updates about this race`;
15111    }
15112    // Generic signup modal
15113    return `Join the running community - it's free!`;
15114  }
15115
15116  /**
15117   * Generate benefits HTML
15118   * @returns {string} Benefits HTML
15119   */
15120  getBenefitsHTML() {
15121    return `
15122      <div class="benefits-section">
15123        ${this.config.benefitsList.map(benefit => `
15124          <div class="benefit-item">
15125            <span class="benefit-check">✓</span>
15126            <span class="benefit-text">${benefit}</span>
15127          </div>
15128        `).join('')}
15129      </div>
15130    `;
15131  }
15132
15133  /**
15134   * Generate profile step HTML
15135   * @returns {string} Profile step HTML
15136   */
15137  getProfileStepHTML() {
15138    return `
15139      <form class="auth-form" id="profile-step-form" method="post" action="/onboarding/update_profile">
15140        <div class="form-group">
15141          <label class="form-label" for="auth-password">
15142            ${this.config.fields.password.label}
15143          </label>
15144          <div class="password-field">
15145            <input
15146              type="password"
15147              id="auth-password-signup"
15148              name="password"
15149              class="form-input"
15150              placeholder="Min 8 characters">
15151            <button type="button" class="password-toggle" onclick="togglePassword('auth-password-signup')">
15152              <svg class="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
15153                <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
15154                <circle cx="12" cy="12" r="3"/>
15155              </svg>
15156              <svg class="eye-off-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;">
15157                <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
15158                <line x1="1" y1="1" x2="23" y2="23"/>
15159              </svg>
15160            </button>
15161          </div>
15162          <div class="form-error" id="password-error"></div>
15163        </div>
15164
15165        <div class="form-row">
15166          <div class="form-group">
15167            <label class="form-label" for="auth-name">
15168              ${this.config.fields.name.label}
15169            </label>
15170            <input
15171              type="text"
15172              id="auth-name"
15173              name="name"
15174              class="form-input"
15175              placeholder="Full name">
15176            <div class="form-error" id="name-error"></div>
15177          </div>
15178
15179          <div class="form-group">
15180            <label class="form-label" for="auth-location">
15181              ${this.config.fields.location.label}
15182            </label>
15183            <input
15184              type="text"
15185              id="auth-location"
15186              name="location"
15187              class="form-input"
15188              placeholder="City, State"
15189              value="${this.userData.location || ''}">
15190            <div class="form-error" id="location-error"></div>
15191          </div>
15192        </div>
15193
15194        <div class="form-row">
15195          <div class="form-group">
15196            <label class="form-label" for="auth-birthday">
15197              ${this.config.fields.birthday.label}
15198            </label>
15199            <input
15200              type="date"
15201              id="auth-birthday"
15202              name="birthday"
15203              class="form-input">
15204          </div>
15205
15206          <div class="form-group">
15207            <label class="form-label" for="auth-gender">
15208              ${this.config.fields.gender.label}
15209            </label>
15210            <select id="auth-gender" name="gender" class="form-input">
15211              <option value="">Select</option>
15212              <option value="male">Male</option>
15213              <option value="female">Female</option>
15214              <option value="other">Other</option>
15215              <option value="prefer-not">Prefer not to say</option>
15216            </select>
15217          </div>
15218        </div>
15219
15220        <p class="helper-text">Help us personalize your race recommendations!</p>
15221
15222        <button type="submit" class="submit-btn">Continue</button>
15223      </form>
15224    `;
15225  }
15226
15227  /**
15228   * Generate success step HTML
15229   * @returns {string} Success step HTML
15230   */
15231  getSuccessStepHTML() {
15232    return `
15233      <div class="success-content">
15234        <div class="success-icon">🎉</div>
15235        <h3 class="success-title">Welcome to RaceMob!</h3>
15236        <p class="success-message">
15237          Your account has been created successfully. 
15238          ${this.config.context ? this.getContextualSuccessMessage() : ''}
15239        </p>
15240        <button type="button" class="submit-btn" id="success-continue">Get Started</button>
15241      </div>
15242    `;
15243  }
15244
15245  /**
15246   * Get contextual success message based on original action
15247   * @returns {string} Contextual message
15248   */
15249  getContextualSuccessMessage() {
15250    if (this.config.context && this.config.context.title) {
15251      return `We've saved "${this.config.context.title}" to your account.`;
15252    }
15253    return '';
15254  }
15255
15256  /**
15257   * Update progress dots based on current step
15258   */
15259  updateProgressDots() {
15260    const dots = this.modalElement.querySelectorAll('.progress-dot');
15261    dots.forEach((dot, index) => {
15262      const step = dot.getAttribute('data-step');
15263      if (step === this.currentStep) {
15264        dot.classList.add('active');
15265      } else if (this.isStepCompleted(step)) {
15266        dot.classList.add('completed');
15267        dot.classList.remove('active');
15268      } else {
15269        dot.classList.remove('active', 'completed');
15270      }
15271    });
15272  }
15273
15274  /**
15275   * Check if a step is completed
15276   * @param {string} step - Step name
15277   * @returns {boolean} Whether step is completed
15278   */
15279  isStepCompleted(step) {
15280    const stepOrder = ['email', 'profile', 'success'];
15281    const currentIndex = stepOrder.indexOf(this.currentStep);
15282    const stepIndex = stepOrder.indexOf(step);
15283    return stepIndex < currentIndex;
15284  }
15285
15286  /**
15287   * Update header content based on current step
15288   */
15289  updateHeaderContent() {
15290    const titleElement = this.modalElement.querySelector('.auth-modal-title');
15291    const subtitleElement = this.modalElement.querySelector('.auth-modal-subtitle');
15292
15293    switch (this.currentStep) {
15294      case 'email':
15295        titleElement.textContent = this.config.title;
15296        subtitleElement.textContent = this.config.subtitle;
15297        break;
15298      case 'profile':
15299        titleElement.textContent = 'Complete Your Profile';
15300        subtitleElement.textContent = 'Tell us a bit about yourself';
15301        break;
15302      case 'success':
15303        // Success step has its own title
15304        break;
15305    }
15306  }
15307
15308  /**
15309   * Attach event listeners
15310   */
15311  attachEventListeners() {
15312    // Close button
15313    const closeBtn = this.modalElement.querySelector('.auth-modal-close');
15314    if (closeBtn) {
15315      closeBtn.addEventListener('click', this.hide);
15316    }
15317    
15318    // Overlay click - track mousedown and mouseup to prevent accidental closes
15319    this.overlayElement.addEventListener('mousedown', this.handleOverlayMouseDown);
15320    this.overlayElement.addEventListener('mouseup', this.handleOverlayMouseUp);
15321    this.overlayElement.addEventListener('click', this.handleOverlayClick);
15322    
15323    // Escape key
15324    document.addEventListener('keydown', this.handleEscapeKey);
15325    
15326    // Form submission
15327    this.modalElement.addEventListener('submit', this.handleFormSubmit);
15328    
15329    // Tab switching
15330    this.modalElement.addEventListener('click', this.handleTabClick.bind(this));
15331    
15332    // Social auth buttons
15333    this.modalElement.addEventListener('click', this.handleSocialAuth.bind(this));
15334
15335    // Back button
15336    this.modalElement.addEventListener('click', this.handleBackButton.bind(this));
15337  }
15338
15339  /**
15340   * Handle overlay mousedown (track if click started on overlay)
15341   * @param {Event} event - Mousedown event
15342   */
15343  handleOverlayMouseDown(event) {
15344    // Track if mousedown started on overlay (not modal content)
15345    this.mouseDownOnOverlay = (event.target === this.overlayElement);
15346  }
15347
15348  /**
15349   * Handle overlay mouseup (track if release happened on overlay)
15350   * @param {Event} event - Mouseup event
15351   */
15352  handleOverlayMouseUp(event) {
15353    // Track if mouseup happened on overlay (not modal content)
15354    this.mouseUpOnOverlay = (event.target === this.overlayElement);
15355  }
15356
15357  /**
15358   * Handle overlay click (close modal)
15359   * Only closes if BOTH mousedown AND mouseup occurred on overlay
15360   * This prevents accidental closes when dragging in/out of modal
15361   * @param {Event} event - Click event
15362   */
15363  handleOverlayClick(event) {
15364    // Only close if mousedown AND mouseup both happened on overlay
15365    if (event.target === this.overlayElement && this.mouseDownOnOverlay && this.mouseUpOnOverlay) {
15366      this.hide();
15367    }
15368    // Reset flags
15369    this.mouseDownOnOverlay = false;
15370    this.mouseUpOnOverlay = false;
15371  }
15372
15373  /**
15374   * Handle escape key press
15375   * @param {Event} event - Keyboard event
15376   */
15377  handleEscapeKey(event) {
15378    if (event.key === 'Escape' && this.isOpen) {
15379      this.hide();
15380    }
15381  }
15382
15383  /**
15384   * Handle form submission
15385   * @param {Event} event - Form submit event
15386   */
15387  handleFormSubmit(event) {
15388    event.preventDefault();
15389
15390    // Prevent double-submission (added 2025-11-26)
15391    if (this.isSubmitting) {
15392      console.warn('AuthModal: BLOCKED duplicate form submission');
15393      return;
15394    }
15395    this.isSubmitting = true;
15396    console.log('AuthModal: Form submission started');
15397
15398    const form = event.target;
15399    const formData = new FormData(form);
15400    const data = Object.fromEntries(formData.entries());
15401
15402    // Handle different form types
15403    if (form.id === 'signin-form') {
15404      this.handleSignInSubmit(data);
15405    } else if (form.id === 'forgot-password-form') {
15406      this.handleForgotPasswordSubmit(data);
15407    } else if (this.currentStep === 'email') {
15408      this.handleEmailStepSubmit(data, event); // Pass event to detect quick signup
15409    } else if (this.currentStep === 'profile') {
15410      this.handleProfileStepSubmit(data);
15411    }
15412  }
15413
15414  /**
15415   * Handle email step form submission
15416   * @param {Object} data - Form data
15417   * @param {Event} event - Form submit event
15418   */
15419  async handleEmailStepSubmit(data, event) {
15420    // Validate email
15421    if (!AuthUtils.validateEmail(data.email)) {
15422      this.showFieldError('email', 'Please enter a valid email address');
15423      return;
15424    }
15425
15426    // Check if this is quick signup (email only, no password)
15427    const isQuickSignup = event?.submitter?.dataset?.quickSignup === 'true';
15428
15429    // NOTE: Email existence check removed to prevent account enumeration
15430    // Server will handle duplicate email detection and return appropriate error
15431
15432    // Store email data
15433    this.userData.email = data.email;
15434
15435    if (isQuickSignup) {
15436      // Quick signup: Create account immediately with random password
15437      await this.handleQuickSignup(data.email);
15438    } else {
15439      // Regular flow: Move to profile step (set password)
15440      this.currentStep = 'profile';
15441      this.renderCurrentStep();
15442    }
15443    
15444
15445  }
15446
15447  /**
15448   * Handle quick signup (email only, auto-generated password)
15449   * Backend generates secure random password
15450   * @param {string} email - User email
15451   */
15452  async handleQuickSignup(email) {
15453    try {
15454      this.showLoadingState();
15455
15456      // Use shared AuthUtils for signup logic
15457      // Don't send verification email yet - we'll send it after profile completion or 60s timer
15458      // Same source the Signup-Start above carried, so start and complete join.
15459      const result = await AuthUtils.quickSignup(email, {
15460        sendVerificationEmail: false,
15461        source: this.signupSource || 'auth_modal'
15462      });
15463
15464      console.log('Quick signup result:', result);
15465
15466      if (result.success) {
15467        // User is now logged in!
15468        console.log('Quick signup successful, user logged in:', result.logged_in);
15469        this.userData = result.user;
15470
15471        // Update page authentication state (no full reload)
15472        await AuthUtils.updatePageAuthState(result.user);
15473
15474        // Execute intended action (e.g., save race) after login
15475        if (this.intendedAction) {
15476          this.executeIntendedAction();
15477        }
15478
15479        // Set flag to reload page when modal closes
15480        this.shouldReloadOnClose = true;
15481
15482        // Schedule verification email timer (60 seconds)
15483        this.scheduleVerificationEmail(result.user.id);
15484
15485        // Show profile modal for optional info (name, location, password)
15486        this.currentStep = 'profile';
15487        this.renderCurrentStep();
15488      } else if (result.email_exists) {
15489        // Email already registered - switch to sign in tab
15490        console.log('Email exists, switching to sign in tab');
15491        this.hideLoadingState(); // Hide loading state first
15492        this.switchToSigninWithEmail(email);
15493
15494        // Update title to be welcoming
15495        const titleElement = this.modalElement.querySelector('.auth-modal-title');
15496        if (titleElement) {
15497          titleElement.textContent = 'Welcome back! 👋';
15498        }
15499
15500        // Highlight OAuth provider if user previously used it
15501        if (result.oauth_provider) {
15502          setTimeout(() => {
15503            this.highlightOAuthProvider(result.oauth_provider);
15504          }, 150);
15505        }
15506
15507        // Show friendly info message instead of error
15508        setTimeout(() => {
15509          const message = result.oauth_provider
15510            ? `We found an account with this email. You can sign in with ${AuthUtils.getProviderName(result.oauth_provider)} or enter your password.`
15511            : 'We found an account with this email. Please enter your password to continue.';
15512          this.showFieldInfo('email', message);
15513        }, 100);
15514      } else {
15515        console.log('Quick signup error - showing submission error');
15516        this.showSubmissionError(result.error);
15517      }
15518    } catch (error) {
15519      console.error('Quick signup error:', error);
15520      this.showSubmissionError('Something went wrong. Please try again.');
15521    }
15522  }
15523
15524
15525  /**
15526   * Generate session ID for tracking
15527   * @returns {string} Session ID
15528   */
15529  generateSessionId() {
15530    return 'auth_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
15531  }
15532
15533  /**
15534   * Schedule verification email timer (T027)
15535   * Sends email after 60 seconds if profile modal still open
15536   * @param {number} userId - User ID
15537   */
15538  scheduleVerificationEmail(userId) {
15539    // Clear any existing timer
15540    if (this.verificationEmailTimer) {
15541      clearTimeout(this.verificationEmailTimer);
15542    }
15543
15544    // Set 60-second timer
15545    this.verificationEmailTimer = setTimeout(async () => {
15546      // Only send if profile modal still open
15547      if (this.currentStep === 'profile' && this.isOpen) {
15548        try {
15549          const result = await AuthUtils.sendVerificationEmail();
15550          console.log('Verification email sent (60s timer):', result.message);
15551        } catch (error) {
15552          console.error('Failed to send verification email (60s timer):', error);
15553        }
15554      }
15555    }, 60000); // 60 seconds
15556  }
15557
15558  /**
15559   * Handle profile step form submission
15560   * @param {Object} data - Form data
15561   */
15562  async handleProfileStepSubmit(data) {
15563    // Validate required fields
15564    if (!this.validateProfileData(data)) {
15565      return;
15566    }
15567
15568    // Clear verification email timer (T028)
15569    if (this.verificationEmailTimer) {
15570      clearTimeout(this.verificationEmailTimer);
15571      this.verificationEmailTimer = null;
15572    }
15573
15574    // Merge with existing data
15575    this.userData = { ...this.userData, ...data };
15576
15577    // Show loading state
15578    this.showLoadingState();
15579
15580    try {
15581      // Submit profile update to /onboarding/update_profile using shared utility
15582      const result = await AuthUtils.updateProfile(this.userData);
15583
15584      if (result.success) {
15585        // Send verification email immediately (T028)
15586        // Wait for email to send before reloading to ensure it completes
15587        try {
15588          const emailResult = await AuthUtils.sendVerificationEmail();
15589          console.log('Verification email sent after profile completion:', emailResult);
15590        } catch (error) {
15591          console.error('Failed to send verification email:', error);
15592          // Continue even if email fails - don't block user
15593        }
15594
15595        // Close modal
15596        this.hide();
15597
15598        // Call success callback
15599        if (this.config.onSuccess) {
15600          this.config.onSuccess(this.userData);
15601        }
15602
15603        // Reload page to refresh all auth-dependent UI (unless explicitly ski
15603pped)
15604        // User is logged in, profile updated, page needs fresh state
15605        if (!this.config.skipReloadOnProfileComplete) {
15606          this.finishAuthNavigation();
15607        }
15608
15609      } else {
15610        this.showSubmissionError(result.error);
15611      }
15612    } catch (error) {
15613      console.error('Profile update error:', error);
15614      this.showSubmissionError('Something went wrong. Please try again.');
15615    }
15616  }
15617
15618  /**
15619   * Validate profile data
15620   * @param {Object} data - Profile data to validate
15621   * @returns {boolean} Whether data is valid
15622   */
15623  validateProfileData(data) {
15624    let isValid = true;
15625
15626    // Clear previous errors
15627    this.clearAllErrors();
15628
15629    // Password is required
15630    if (!data.password || data.password.length < 8) {
15631      this.showFieldError('password', 'Password must be at least 8 characters');
15632      isValid = false;
15633    }
15634
15635    // Name and location are optional - no validation needed
15636    // Birthday and gender are also optional
15637
15638    return isValid;
15639  }
15640
15641  /**
15642   * Show field error
15643   * @param {string} fieldName - Field name
15644   * @param {string} message - Error message
15645   */
15646  showFieldError(fieldName, message) {
15647    const errorElement = this.modalElement.querySelector(`#${fieldName}-error`);
15648    const inputElement = this.modalElement.querySelector(`#auth-${fieldName}`);
15649
15650    if (errorElement) {
15651      errorElement.textContent = message;
15652      errorElement.style.display = 'block';
15653    }
15654
15655    if (inputElement) {
15656      inputElement.classList.add('error');
15657    }
15658  }
15659
15660  /**
15661   * Show friendly info message (not an error)
15662   * @param {string} fieldName - Field name
15663   * @param {string} message - Info message
15664   */
15665  showFieldInfo(fieldName, message) {
15666    const errorElement = this.modalElement.querySelector(`#${fieldName}-error`);
15667    const inputElement = this.modalElement.querySelector(`#auth-${fieldName}`);
15668
15669    if (errorElement) {
15670      errorElement.textContent = message;
15671      errorElement.style.display = 'block';
15672      errorElement.style.color = '#555'; // Neutral color instead of red
15673    }
15674
15675    // Don't add error class to input - keep it looking normal
15676    if (inputElement) {
15677      inputElement.classList.remove('error');
15678    }
15679  }
15680
15681  /**
15682   * Clear all form errors
15683   */
15684  clearAllErrors() {
15685    const errorElements = this.modalElement.querySelectorAll('.form-error');
15686    const inputElements = this.modalElement.querySelectorAll('.form-input');
15687    
15688    errorElements.forEach(el => {
15689      el.style.display = 'none';
15690      el.textContent = '';
15691    });
15692    
15693    inputElements.forEach(el => {
15694      el.classList.remove('error');
15695    });
15696  }
15697
15698  /**
15699   * Show loading state during form submission
15700   */
15701  showLoadingState() {
15702    const submitBtn = this.modalElement.querySelector('.submit-btn.loading') ||
15703                      this.modalElement.querySelector('.submit-btn.primary') ||
15704                      this.modalElement.querySelector('.submit-btn');
15705    if (submitBtn) {
15706      submitBtn.disabled = true;
15707      submitBtn.textContent = 'Creating Account...';
15708      submitBtn.classList.add('loading');
15709      // Store reference to the button we disabled so we can re-enable the correct one
15710      this.currentSubmitBtn = submitBtn;
15711    }
15712  }
15713
15714  /**
15715   * Show error message using notification system
15716   * @param {string} message - Error message
15717   */
15718  showError(message) {
15719    if (window.UIHelpers && window.UIHelpers.showNotification) {
15720      window.UIHelpers.showNotification('error', 'Error', message);
15721    } else if (window.showNotification) {
15722      window.showNotification('error', 'Error', message);
15723    } else {
15724      console.error('Error:', message);
15725      alert(message); // Keep as fallback
15726    }
15727  }
15728
15729  /**
15730   * Show submission error
15731   * @param {string} message - Error message
15732   */
15733  showSubmissionError(message) {
15734    // Reset submission lock
15735    this.isSubmitting = false;
15736
15737    // Use the stored reference to the button we disabled, with fallback
15738    const submitBtn = this.currentSubmitBtn ||
15739                      this.modalElement.querySelector('.submit-btn.loading') ||
15740                      this.modalElement.querySelector('.submit-btn.primary') ||
15741                      this.modalElement.querySelector('.submit-btn');
15742
15743    if (submitBtn) {
15744      submitBtn.disabled = false;
15745      // Restore appropriate button text based on context
15746      if (this.currentStep === 'email') {
15747        submitBtn.textContent = 'Quick Signup';
15748      } else if (this.currentStep === 'profile') {
15749        submitBtn.textContent = 'Complete Signup';
15750      } else {
15751        submitBtn.textContent = 'Submit';
15752      }
15753      submitBtn.classList.remove('loading');
15754      // Clear the reference
15755      this.currentSubmitBtn = null;
15756    }
15757
15758    // Show general error message
15759    this.showError(message);
15760  }
15761
15762  /**
15763   * Handle tab click (Sign Up / Sign In)
15764   * @param {Event} event - Click event
15765   */
15766  handleTabClick(event) {
15767    if (event.target.classList.contains('auth-tab')) {
15768      const tab = event.target.getAttribute('data-tab');
15769
15770      // Update active tab
15771      this.modalElement.querySelectorAll('.auth-tab').forEach(tabEl => {
15772        tabEl.classList.remove('active');
15773      });
15774      event.target.classList.add('active');
15775
15776      // Switch between signup and signin forms
15777      this.currentAuthTab = tab;
15778
15779      // Update title and subtitle based on tab
15780      this.updateTitleForTab(tab);
15781
15782      this.renderAuthForm();
15783    }
15784  }
15785
15786  /**
15787   * Update modal title and subtitle based on active tab
15788   * @param {string} tab - 'signup' or 'signin'
15789   */
15790  updateTitleForTab(tab) {
15791    const titleElement = this.modalElement.querySelector('.auth-modal-title');
15792    const subtitleElement = this.modalElement.querySelector('.auth-modal-subtitle');
15793
15794    if (!titleElement || !subtitleElement) return;
15795
15796    if (tab === 'signup') {
15797      titleElement.textContent = 'Welcome to the Mob!';
15798      subtitleElement.textContent = 'Ready to find your running crew?';
15799    } else {
15800      titleElement.textContent = 'Welcome Back!';
15801      subtitleElement.textContent = 'Sign in to your account';
15802    }
15803  }
15804
15805  /**
15806   * Handle social auth button click
15807   * @param {Event} event - Click event
15808   */
15809  handleSocialAuth(event) {
15810    const socialBtn = event.target.closest('.social-btn');
15811    if (socialBtn) {
15812      const provider = socialBtn.getAttribute('data-provider');
15813      
15814
15815      
15816      // Create a POST form to handle OAuth properly (OmniAuth 2.x requires POST)
15817      if (provider === 'google' || provider === 'facebook') {
15818        this.submitSocialAuthForm(provider);
15819      }
15820    }
15821  }
15822  
15823  /**
15824   * Submit social auth form with POST method and CSRF token
15825   * @param {string} provider - OAuth provider (google, facebook, etc.)
15826   */
15827  submitSocialAuthForm(provider) {
15828    // Map provider names to OmniAuth provider names
15829    const providerMap = {
15830      'google': 'google_oauth2',
15831      'facebook': 'facebook'
15832    };
15833    
15834    const authProvider = providerMap[provider] || provider;
15835    
15836    const form = document.createElement('form');
15837    form.method = 'POST';
15838    form.action = `/auth/${authProvider}`;
15839    form.style.display = 'none';
15840    
15841    // Add CSRF token
15842    const csrfToken = AuthUtils.getCSRFToken();
15843    if (csrfToken) {
15844      const csrfInput = document.createElement('input');
15845      csrfInput.type = 'hidden';
15846      csrfInput.name = 'authenticity_token';
15847      csrfInput.value = csrfToken;
15848      form.appendChild(csrfInput);
15849    }
15850    
15851    // Store intended action for post-auth execution
15852    if (this.intendedAction && this.intendedData) {
15853      sessionStorage.setItem('racemob_intended_action', JSON.stringify({
15854        action: this.intendedAction,
15855        data: this.intendedData,
15856        timestamp: Date.now()
15857      }));
15858    }
15859    
15860    document.body.appendChild(form);
15861    form.submit();
15862  }
15863
15864  /**
15865   * Handle back button click
15866   * @param {Event} event - Click event
15867   */
15868  handleBackButton(event) {
15869    if (event.target.classList.contains('back-btn')) {
15870      if (this.currentStep === 'profile') {
15871        // After quick signup, Close button just closes the modal (reload will happen)
15872        if (this.shouldReloadOnClose) {
15873          this.hide();
15874        } else {
15875          // Normal onboarding flow - go back to email step
15876          this.currentStep = 'email';
15877          this.renderCurrentStep();
15878        }
15879      }
15880    }
15881  }
15882
15883  /**
15884   * Animate modal in
15885   */
15886  animateIn() {
15887    // Ensure modal is visible first
15888    this.overlayElement.style.display = 'flex';
15889
15890    // Set initial state
15891    this.overlayElement.style.opacity = '0';
15892    this.modalElement.style.transform = 'translateY(20px)';
15893    this.modalElement.style.opacity = '0';
15894
15895    // Force reflow
15896    this.overlayElement.offsetHeight;
15897
15898    // Add transition classes
15899    this.overlayElement.style.transition = 'opacity 0.2s ease';
15900    this.modalElement.style.transition = 'all 0.3s ease';
15901
15902    // Animate to final state
15903    requestAnimationFrame(() => {
15904      this.overlayElement.style.opacity = '1';
15905      this.modalElement.style.transform = 'translateY(0)';
15906      this.modalElement.style.opacity = '1';
15907    });
15908  }
15909
15910  /**
15911   * Animate modal out
15912   * @param {Function} callback - Callback to execute after animation
15913   */
15914  animateOut(callback) {
15915    this.overlayElement.style.transition = 'opacity 0.2s ease';
15916    this.modalElement.style.transition = 'all 0.3s ease';
15917    
15918    this.overlayElement.style.opacity = '0';
15919    this.modalElement.style.transform = 'translateY(20px)';
15920    this.modalElement.style.opacity = '0';
15921    
15922    setTimeout(callback, 300);
15923  }
15924
15925  /**
15926   * Focus trap for accessibility
15927   */
15928  trapFocus() {
15929    const focusableElements = this.modalElement.querySelectorAll(
15930      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
15931    );
15932    
15933    if (focusableElements.length > 0) {
15934      focusableElements[0].focus();
15935    }
15936  }
15937
15938  /**
15939   * Clean up modal elements and event listeners
15940   */
15941  cleanup() {
15942    // Remove event listeners
15943    document.removeEventListener('keydown', this.handleEscapeKey);
15944    
15945    // Remove modal elements
15946    if (this.overlayElement && this.overlayElement.parentNode) {
15947      this.overlayElement.parentNode.removeChild(this.overlayElement);
15948    }
15949    
15950    this.modalElement = null;
15951    this.overlayElement = null;
15952  }
15953
15954
15955  /**
15956   * Switch to signin tab and prefill email with welcome message
15957   * @param {string} email - Email to prefill
15958   */
15959  switchToSigninWithEmail(email) {
15960    // Switch to signin tab
15961    this.currentAuthTab = 'signin';
15962    
15963    // Update tab UI
15964    this.modalElement.querySelectorAll('.auth-tab').forEach(tab => {
15965      tab.classList.remove('active');
15966      if (tab.getAttribute('data-tab') === 'signin') {
15967        tab.classList.add('active');
15968      }
15969    });
15970    
15971    // Render signin form
15972    this.renderAuthForm();
15973    
15974    // Prefill email and focus password
15975    setTimeout(() => {
15976      const emailInput = this.modalElement.querySelector('#auth-email');
15977      const passwordInput = this.modalElement.querySelector('#auth-password');
15978      
15979      if (emailInput) {
15980        // Use defaultValue to avoid triggering "unsaved changes" detection
15981        emailInput.defaultValue = email;
15982        emailInput.value = email;
15983        emailInput.readOnly = true; // Prevent changes to recognized email
15984        
15985        // Mark the form as clean after setting the initial value
15986        const form = emailInput.closest('form');
15987        if (form && form.reset) {
15988          // Save the current values, reset, then restore to mark as clean
15989          const currentEmail = emailInput.value;
15990          const currentReadOnly = emailInput.readOnly;
15991          form.reset();
15992          emailInput.defaultValue = currentEmail;
15993          emailInput.value = currentEmail;
15994          emailInput.readOnly = currentReadOnly;
15995        }
15996      }
15997      
15998      if (passwordInput) {
15999        passwordInput.focus();
16000      }
16001      
16002      // Show welcome back message
16003      this.showWelcomeBackMessage(email);
16004    }, 100);
16005  }
16006
16007  /**
16008   * Show welcome back message for recognized email
16009   * @param {string} email - User's email
16010   */
16011  showWelcomeBackMessage(email) {
16012    const contentContainer = this.modalElement.querySelector('.auth-modal-content');
16013    if (contentContainer) {
16014      // Remove any existing message
16015      const existingMessage = contentContainer.querySelector('.welcome-back-message');
16016      if (existingMessage) {
16017        existingMessage.remove();
16018      }
16019      
16020      // Add welcome back message at the top of the form
16021      const messageHTML = `
16022        <div class="welcome-back-message">
16023          <div class="message-content">
16024            <span class="welcome-icon">👋</span>
16025            <div class="welcome-text">
16026              <strong>Welcome back!</strong>
16027              <p>We found an account with ${email}. Please enter your password to continue.</p>
16028            </div>
16029          </div>
16030        </div>
16031      `;
16032      
16033      contentContainer.insertAdjacentHTML('afterbegin', messageHTML);
16034    }
16035  }
16036
16037  /**
16038   * Show loading indicator during email checking
16039   */
16040  showEmailCheckingLoader() {
16041    const emailInput = this.modalElement.querySelector('#auth-email');
16042    if (emailInput) {
16043      // Add loading class to input
16044      emailInput.classList.add('checking');
16045      
16046      // Add loading indicator after input
16047      const loadingHTML = `
16048        <div class="email-checking-loader">
16049          <span class="loader-spinner"></span>
16050          <span class="loader-text">Checking email...</span>
16051        </div>
16052      `;
16053      
16054      emailInput.parentNode.insertAdjacentHTML('beforeend', loadingHTML);
16055    }
16056  }
16057
16058  /**
16059   * Hide email checking loading indicator
16060   */
16061  hideEmailCheckingLoader() {
16062    const emailInput = this.modalElement.querySelector('#auth-email');
16063    const loader = this.modalElement.querySelector('.email-checking-loader');
16064    
16065    if (emailInput) {
16066      emailInput.classList.remove('checking');
16067    }
16068    
16069    if (loader) {
16070      loader.remove();
16071    }
16072  }
16073
16074  /**
16075   * Render the appropriate auth form based on current tab
16076   */
16077  renderAuthForm() {
16078    const formContainer = this.modalElement.querySelector('.auth-form');
16079    if (!formContainer) {
16080      // If no existing form, render the complete structure
16081      const contentContainer = this.modalElement.querySelector('.auth-modal-content');
16082      if (contentContainer) {
16083        contentContainer.innerHTML = this.getEmailStepHTML();
16084        return;
16085      }
16086    }
16087
16088    // Replace only the form part, keeping benefits, tabs, and social buttons
16089    if (this.currentAuthTab === 'signin') {
16090      formContainer.outerHTML = this.getSignInFormHTML();
16091    } else {
16092      formContainer.outerHTML = this.getSignUpFormHTML();
16093    }
16094  }
16095
16096  /**
16097   * Generate signin form HTML (form only, no social buttons)
16098   * @returns {string} Signin form HTML
16099   */
16100  getSignInFormHTML() {
16101    return `
16102      <form class="auth-form" id="signin-form" method="post" action="/login">
16103        <div class="form-group">
16104          <label class="form-label" for="auth-email">${this.config.fields.email.label}</label>
16105          <input
16106            type="email"
16107            id="auth-email"
16108            name="email"
16109            class="form-input"
16110            placeholder="[email protected]"
16111            required>
16112          <div class="form-error" id="email-error"></div>
16113        </div>
16114        
16115        <div class="form-group">
16116          <label class="form-label" for="auth-password">Password</label>
16117          <div class="password-field">
16118            <input
16119              type="password"
16120              id="auth-password"
16121              name="password"
16122              class="form-input"
16123              placeholder="Enter your password"
16124              required>
16125            <button type="button" class="password-toggle" onclick="togglePassword('auth-password')">
16126              <svg class="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
16127                <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
16128                <circle cx="12" cy="12" r="3"/>
16129              </svg>
16130              <svg class="eye-off-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: none;">
16131                <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
16132                <line x1="1" y1="1" x2="23" y2="23"/>
16133              </svg>
16134            </button>
16135          </div>
16136          <div class="form-error" id="password-error"></div>
16137        </div>
16138
16139        <div class="form-options">
16140          <a href="#" class="forgot-password-link" onclick="window.AuthModal.showForgotPassword(); return false;">
16141            Forgot password?
16142          </a>
16143        </div>
16144
16145        <button type="submit" class="submit-btn">Sign In</button>
16146      </form>
16147    `;
16148  }
16149
16150  /**
16151   * Generate signup form HTML (form only, no social buttons)
16152   * @returns {string} Signup form HTML
16153   */
16154  getSignUpFormHTML() {
16155    return `
16156      <form class="auth-form" id="email-step-form" method="post" action="/onboarding/save_email">
16157        <div class="form-group">
16158          <label class="form-label" for="auth-email">${this.config.fields.email.label}</label>
16159          <input
16160            type="email"
16161            id="auth-email"
16162            name="email"
16163            class="form-input"
16164            placeholder="[email protected]"
16165            required>
16166          <div class="form-error" id="email-error"></div>
16167        </div>
16168
16169        <p class="helper-text">${this.getContextualHelperText()}</p>
16170
16171        <button type="submit" class="submit-btn primary" data-quick-signup="true">Continue</button>
16172      </form>
16173    `;
16174  }
16175
16176  /**
16177   * Show forgot password form within the modal
16178   */
16179  showForgotPassword() {
16180    const formContainer = this.modalElement.querySelector('.auth-form');
16181    if (!formContainer) return;
16182
16183    formContainer.outerHTML = this.getForgotPasswordFormHTML();
16184  }
16185
16186  /**
16187   * Generate forgot password form HTML (form only)
16188   * @returns {string} Forgot password form HTML
16189   */
16190  getForgotPasswordFormHTML() {
16191    return `
16192      <form class="auth-form" id="forgot-password-form" method="post" action="/passwords">
16193        <div class="forgot-header">
16194          <h3>Reset Your Password</h3>
16195          <p>Enter your email address and we'll send you a link to reset your password.</p>
16196        </div>
16197        
16198        <div class="form-group">
16199          <label class="form-label" for="reset-email">Email</label>
16200          <input 
16201            type="email" 
16202            id="reset-email" 
16203            name="login"
16204            class="form-input" 
16205            placeholder="[email protected]" 
16206            required>
16207          <div class="form-error" id="reset-email-error"></div>
16208        </div>
16209        
16210        <div class="forgot-actions">
16211          <button type="submit" class="submit-btn">Send Reset Link</button>
16212          <a href="#" class="back-to-signin" onclick="window.AuthModal.showSignIn(); return false;">
16213            ← Back to Sign In
16214          </a>
16215        </div>
16216      </form>
16217    `;
16218  }
16219
16220  /**
16221   * Return to signin form from forgot password
16222   */
16223  showSignIn() {
16224    this.currentAuthTab = 'signin';
16225    this.renderAuthForm();
16226  }
16227
16228  /**
16229   * Handle signin form submission
16230   *
16231   * Reads the standardized envelope from AuthUtils.signIn (PR #289):
16232   *   { success: true,  user, redirect_to? }
16233   *   { success: false, error, oauth_provider?, email? }
16234   *
16235   * AuthUtils.signIn only throws on network/parse failures post-PR #289 —
16236   * the OAuth-provider hint arrives via the failure object, not via a
16237   * thrown error with .oauth_provider attached.
16238   *
16239   * @param {Object} data - Form data
16240   */
16241  async handleSignInSubmit(data) {
16242    try {
16243      this.showLoadingState();
16244
16245      const result = await AuthUtils.signIn(data.email, data.password, data.remember_me || false,
16246        { intendedAction: this.config.intendedAction });
16247
16248      if (result.success === false) {
16249        if (result.oauth_provider) {
16250          this.showFieldError('password', 'Invalid password');
16251          this.highlightOAuthProvider(result.oauth_provider);
16252
16253          setTimeout(() => {
16254            const passwordError = this.modalElement.querySelector('#password-error');
16255            if (passwordError) {
16256              passwordError.innerHTML = `Invalid password. <br><small style="font-weight: normal;">Hint: You previously signed in with ${AuthUtils.getProviderName(result.oauth_provider)}.</small>`;
16257            }
16258          }, 50);
16259        } else {
16260          this.showFieldError('password', result.error || 'Invalid email or password');
16261        }
16262        return;
16263      }
16264
16265      // Success — propagate redirect_to so the modal navigates to the
16266      // server's intended destination instead of reloading the originating
16267      // page (which would destroy the modal mid-redirect).
16268      this.handleLoginSuccess({
16269        success: true,
16270        user: result.user,
16271        redirect_needed: !!result.redirect_to,
16272        redirect_url: result.redirect_to,
16273        message: 'Welcome back!'
16274      });
16275
16276    } catch (error) {
16277      // Network or parse failure only. AuthUtils does not throw for
16278      // wrong-credentials or oauth-hint cases anymore — those return a
16279      // failure object handled above.
16280      this.showFieldError('password', error.message || 'Invalid email or password');
16281    } finally {
16282      this.hideLoadingState();
16283    }
16284  }
16285
16286  /**
16287   * Handle successful login
16288   * @param {Object} result - Login response data
16289   */
16290  handleLoginSuccess(result) {
16291    // Call success callback if provided
16292    if (this.config.onSuccess) {
16293      this.config.onSuccess(result.user);
16294    }
16295
16296    // Close modal first
16297    this.hide();
16298
16299    // Handle redirect if needed (onboarding, etc.)
16300    if (result.redirect_needed && result.redirect_url) {
16301      window.location.replace(result.redirect_url);
16302      return;
16303    }
16304
16305    // Try to execute intended action if available (for race-specific actions like save/going).
16306    // Skip when an onSuccess callback already ran above — that caller (e.g. race
16307    // detail page Save, claim-race, registration) already performed the action
16308    // itself; dispatching again here would double-fire it / show a redundant
16309    // generic toast on top of the real one.
16310    if (!this.config.onSuccess && this.intendedAction && this.intendedAction !== 'login_success' && typeof window.executeRaceAction === 'function') {
16311      try {
16312        window.executeRaceAction(this.intendedAction, this.intendedData?.raceData || this.intendedData);
16313        this.clearIntendedAction();
16314        return;
16315      } catch (error) {
16316        console.warn('Failed to execute intended action:', error);
16317      }
16318    }
16319
16320    // Default: land the user (postAuthUrl when the modal replaced a link, else reload)
16321    this.finishAuthNavigation();
16322  }
16323
16324  /**
16325   * Refresh page with intended action parameters
16326   */
16327  redirectWithIntendedAction() {
16328    const currentUrl = new URL(window.location.href);
16329    
16330    // Add intended action parameters
16331    if (this.intendedAction) {
16332      currentUrl.searchParams.set('intended_action', this.intendedAction);
16333      currentUrl.searchParams.set('action_type', this.intendedAction); // Always set action_type
16334    }
16335
16336    if (this.intendedData) {
16337      // Store intended data in session storage for complex data
16338      if (this.intendedData.raceData) {
16339        currentUrl.searchParams.set('race_id', this.intendedData.raceData.id || '');
16340      }
16341
16342      // Store full intended data in sessionStorage for immediate retrieval
16343      sessionStorage.setItem('racemob_intended_action', JSON.stringify({
16344        action: this.intendedAction,
16345        data: this.intendedData,
16346        timestamp: Date.now()
16347      }));
16348    }
16349    
16350    // Replace the current page with action parameters to avoid form change warnings
16351    window.location.replace(currentUrl.toString());
16352  }
16353
16354  /**
16355   * Clear intended action state
16356   */
16357  clearIntendedAction() {
16358    this.intendedAction = null;
16359    this.intendedData = null;
16360  }
16361
16362  /**
16363   * Update navigation to show logged-in state (deprecated - using page refresh instead)
16364   */
16365  updateNavigationForLoggedInUser() {
16366    // This method is deprecated since we now use page refresh for reliable state updates
16367    // Keeping for backward compatibility but not actively used
16368
16369  }
16370
16371  // Removed complex navigation refresh methods since we now use page refresh
16372  // This provides a much more reliable authentication state update
16373
16374  /**
16375   * Handle forgot password form submission
16376   * @param {Object} data - Form data
16377   */
16378  async handleForgotPasswordSubmit(data) {
16379    try {
16380      this.showLoadingState();
16381      
16382      const response = await fetch('/passwords', {
16383        method: 'POST',
16384        headers: {
16385          'Content-Type': 'application/json',
16386          'Accept': 'application/json',
16387          'X-CSRF-Token': AuthUtils.getCSRFToken()
16388        },
16389        credentials: 'same-origin',
16390        body: JSON.stringify({ login: data.login })
16391      });
16392      
16393      const result = await response.json();
16394      
16395      if (response.ok) {
16396        // Show success message
16397        this.showForgotPasswordSuccess(data.login);
16398      } else {
16399        this.showFieldError('reset-email', 'Unable to send reset email. Please try again.');
16400      }
16401    } catch (error) {
16402      console.error('AuthModal: Forgot password failed', error);
16403      this.showFieldError('reset-email', 'Something went wrong. Please try again.');
16404    } finally {
16405      this.hideLoadingState();
16406    }
16407  }
16408
16409  /**
16410   * Show forgot password success message
16411   * @param {string} email - Email address
16412   */
16413  showForgotPasswordSuccess(email) {
16414    const formContainer = this.modalElement.querySelector('.auth-form');
16415    if (!formContainer) return;
16416
16417    formContainer.outerHTML = `
16418      <div class="auth-form forgot-success">
16419        <div class="success-icon">✉️</div>
16420        <h3>Check Your Email</h3>
16421        <p>We've sent password reset instructions to <strong>${email}</strong></p>
16422        <p class="helper-text">If you don't see the email, check your spam folder.</p>
16423        
16424        <div class="success-actions">
16425          <button type="button" class="submit-btn" onclick="window.AuthModal.showSignIn()">
16426            Back to Sign In
16427          </button>
16428        </div>
16429      </div>
16430    `;
16431  }
16432
16433  /**
16434   * Show loading state on submit button
16435   */
16436  showLoadingState() {
16437    const submitBtn = this.modalElement.querySelector('.submit-btn');
16438    if (submitBtn) {
16439      submitBtn.classList.add('loading');
16440      submitBtn.disabled = true;
16441      
16442      const originalText = submitBtn.textContent;
16443      submitBtn.setAttribute('data-original-text', originalText);
16444      
16445      if (originalText.includes('Sign In')) {
16446        submitBtn.textContent = 'Signing In...';
16447      } else if (originalText.includes('Send')) {
16448        submitBtn.textContent = 'Sending...';
16449      } else {
16450        submitBtn.textContent = 'Loading...';
16451      }
16452    }
16453  }
16454
16455  /**
16456   * Hide loading state on submit button
16457   */
16458  hideLoadingState() {
16459    // Reset submission lock
16460    this.isSubmitting = false;
16461
16462    const submitBtn = this.modalElement.querySelector('.submit-btn');
16463    if (submitBtn) {
16464      submitBtn.classList.remove('loading');
16465      submitBtn.disabled = false;
16466
16467      const originalText = submitBtn.getAttribute('data-original-text');
16468      if (originalText) {
16469        submitBtn.textContent = originalText;
16470        submitBtn.removeAttribute('data-original-text');
16471      }
16472    }
16473  }
16474
16475  /**
16476   * Execute the intended action after successful authentication
16477   */
16478  executeIntendedAction() {
16479    if (!this.intendedAction) return;
16480
16481    try {
16482      switch (this.intendedAction) {
16483        case 'save_race':
16484          if (this.intendedData && this.intendedData.raceId) {
16485            this.executeSaveRace(this.intendedData.raceId);
16486          }
16487          break;
16488          
16489        case 'write_review':
16490          if (this.intendedData && this.intendedData.raceId) {
16491            this.executeWriteReview(this.intendedData.raceId);
16492          }
16493          break;
16494          
16495        case 'open_modal':
16496          if (this.intendedData && this.intendedData.modalType) {
16497            this.executeOpenModal(this.intendedData.modalType, this.intendedData.modalData);
16498          }
16499          break;
16500          
16501        case 'custom_callback':
16502          if (this.intendedData && this.intendedData.callback) {
16503            this.intendedData.callback(this.intendedData.data);
16504          }
16505          break;
16506          
16507        default:
16508
16509      }
16510    } catch (error) {
16511
16512    } finally {
16513      // Clear intended action after execution
16514      this.clearIntendedAction();
16515    }
16516  }
16517
16518  /**
16519   * Execute save race action
16520   * @param {number} raceId - Race ID to save
16521   */
16522  executeSaveRace(raceId) {
16523    // Try different save race implementations
16524    if (window.saveRaceForUser) {
16525      window.saveRaceForUser({ raceId: raceId });
16526      return;
16527    }
16528
16529    // Use the existing toggleSaveRace function by finding or creating a button element
16530    if (typeof window.toggleSaveRace === 'function') {
16531      // Create a temporary button element with the race data
16532      const $tempButton = $('<button>')
16533        .data('race-id', raceId)
16534        .data('saved', false); // We want to save, not unsave
16535
16536      window.toggleSaveRace($tempButton[0]);
16537      return;
16538    }
16539
16540    // Fallback: Direct AJAX call to save race
16541    $.ajax({
16542      url: '/searches_fast/save_race',
16543      type: 'POST',
16544      data: {
16545        race_occurence_id: raceId,
16546        authenticity_token: $('meta[name="csrf-token"]').attr('content')
16547      },
16548      dataType: 'json',
16549      success: function(response) {
16550        if (response.success) {
16551          console.log('Race saved successfully:', raceId);
16552          if (typeof window.showSuccessMessage === 'function') {
16553            window.showSuccessMessage('Race saved to your profile!');
16554          }
16555        }
16556      },
16557      error: function(xhr, status, error) {
16558        console.error('Failed to save race:', error);
16559        if (typeof window.showSuccessMessage === 'function') {
16560          window.showSuccessMessage('Welcome! You can now save races.');
16561        }
16562      }
16563    });
16564  }
16565
16566  /**
16567   * Execute write review action
16568   * @param {number} raceId - Race ID to review
16569   */
16570  executeWriteReview(raceId) {
16571    // Open the review modal
16572    if (window.showReviewModal) {
16573      window.showReviewModal(raceId);
16574    } else {
16575
16576    }
16577  }
16578
16579  /**
16580   * Execute open modal action
16581   * @param {string} modalType - Type of modal to open
16582   * @param {Object} modalData - Data for the modal
16583   */
16584  executeOpenModal(modalType, modalData) {
16585    switch (modalType) {
16586      case 'review':
16587        if (window.showReviewModal && modalData.raceId) {
16588          window.showReviewModal(modalData.raceId);
16589        }
16590        break;
16591        
16592      case 'photo':
16593        if (window.showPhotoModal && modalData.raceId) {
16594          window.showPhotoModal(modalData.raceId);
16595        }
16596        break;
16597        
16598      default:
16599
16600    }
16601  }
16602
16603  /**
16604   * Clear stored intended action
16605   */
16606  clearIntendedAction() {
16607    this.intendedAction = null;
16608    this.intendedData = null;
16609  }
16610
16611  /**
16612   * Show success message/toast
16613   * @param {string} message - Success message to show
16614   */
16615  showSuccessMessage(message) {
16616    // Create a simple toast notification
16617    const toast = document.createElement('div');
16618    toast.className = 'auth-success-toast';
16619    toast.textContent = message;
16620    
16621    // Add styles
16622    Object.assign(toast.style, {
16623      position: 'fixed',
16624      top: '20px',
16625      right: '20px',
16626      backgroundColor: '#4CAF50',
16627      color: 'white',
16628      padding: '12px 24px',
16629      borderRadius: '8px',
16630      boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
16631      zIndex: '13000', // Above auth modal overlay (12000)
16632      fontSize: '14px',
16633      fontWeight: '500',
16634      maxWidth: '300px',
16635      opacity: '0',
16636      transform: 'translateY(-10px)',
16637      transition: 'all 0.3s ease'
16638    });
16639    
16640    document.body.appendChild(toast);
16641    
16642    // Animate in
16643    setTimeout(() => {
16644      toast.style.opacity = '1';
16645      toast.style.transform = 'translateY(0)';
16646    }, 100);
16647    
16648    // Remove after 4 seconds
16649    setTimeout(() => {
16650      toast.style.opacity = '0';
16651      toast.style.transform = 'translateY(-10px)';
16652      setTimeout(() => {
16653        if (toast.parentNode) {
16654          toast.parentNode.removeChild(toast);
16655        }
16656      }, 300);
16657    }, 4000);
16658  }
16659
16660  /**
16661   * Highlight OAuth provider button
16662   * @param {string} provider - OAuth provider (google_oauth2, facebook)
16663   */
16664  highlightOAuthProvider(provider) {
16665    const providerClass = provider === 'google_oauth2' ? 'google-btn' : 'facebook-btn';
16666    const button = this.modalElement.querySelector(`.${providerClass}`);
16667
16668    if (button) {
16669      // Add pulsing animation
16670      button.style.animation = 'pulse 1.5s ease-in-out infinite';
16671      button.style.boxShadow = '0 0 0 3px rgba(66, 133, 244, 0.3)';
16672
16673      // Add CSS for pulse animation if it doesn't exist
16674      if (!document.getElementById('oauth-highlight-styles')) {
16675        const style = document.createElement('style');
16676        style.id = 'oauth-highlight-styles';
16677        style.textContent = `
16678          @keyframes pulse {
16679            0%, 100% { transform: scale(1); }
16680            50% { transform: scale(1.05); }
16681          }
16682        `;
16683        document.head.appendChild(style);
16684      }
16685    }
16686  }
16687}
16688
16689// Create global instance
16690
16691window.AuthModal = new AuthModal();
16692
16693
16694// ===== JAVASCRIPT API EXTENSIONS =====
16695
16696
16697/**
16698 * AuthModal API - Convenient methods for common use cases
16699 */
16700window.AuthModalAPI = {
16701  // Quick signup modal
16702  signup: function(config = {}) {
16703    const defaultConfig = {
16704      title: 'Join RaceMob Today',
16705      subtitle: 'Connect with runners worldwide',
16706      benefits: true,
16707      benefitsList: ['Track your races', 'Connect with runners', 'Get notifications'],
16708      defaultTab: 'signup',
16709      intendedAction: 'login_success',
16710      intendedData: { showToast: true }
16711    };
16712
16713    window.AuthModal.show({
16714      ...defaultConfig,
16715      ...config
16716    });
16717  },
16718
16719  // Quick signin modal
16720  signin: function(config = {}) {
16721    const defaultConfig = {
16722      title: 'Welcome Back!',
16723      subtitle: 'Sign in to your account',
16724      benefits: false,
16725      defaultTab: 'signin',
16726      intendedAction: 'login_success',
16727      intendedData: { showToast: true }
16728    };
16729
16730    window.AuthModal.show({
16731      ...defaultConfig,
16732      ...config
16733    });
16734  },
16735
16736  // Race-specific modals
16737  saveRace: function(race, config = {}) {
16738    window.AuthModal.show({
16739      context: {
16740        icon: '💾',
16741        title: race.name,
16742        subtitle: `${race.date} • ${race.location}`
16743      },
16744      title: 'Want to track this race?',
16745      subtitle: 'Never miss registration',
16746      benefits: true,
16747      benefitsList: ['Price alerts', 'Registration reminders', 'See who\'s going'],
16748      intendedAction: 'save',
16749      intendedData: { raceData: race },
16750      ...config
16751    });
16752  },
16753
16754  // Review race modal
16755  reviewRace: function(race, config = {}) {
16756    window.AuthModal.show({
16757      context: {
16758        icon: '⭐',
16759        title: race.name,
16760        subtitle: `${race.date} • ${race.location}`
16761      },
16762      title: 'Share your experience',
16763      subtitle: 'Help other runners by reviewing this race',
16764      benefits: true,
16765      benefitsList: ['Help the community', 'Share your story', 'Rate the race'],
16766      intendedAction: 'review',
16767      intendedData: { raceData: race },
16768      defaultTab: 'signin', // Prefer signin for reviews
16769      ...config
16770    });
16771  },
16772
16773  // Generic modal opener
16774  withAuth: function(action, data, config = {}) {
16775    window.AuthModal.show({
16776      title: 'Sign in to continue',
16777      subtitle: 'Access this feature with your account',
16778      intendedAction: action,
16779      intendedData: data,
16780      defaultTab: 'signin',
16781      ...config
16782    });
16783  },
16784
16785  markGoing: function(race, config = {}) {
16786    window.AuthModal.show({
16787      context: {
16788        icon: '🎉',
16789        title: race.name,
16790        subtitle: `${race.date} • ${race.location}`
16791      },
16792      title: "You're going to this race!",
16793      subtitle: 'Let other runners know',
16794      benefits: true,
16795      benefitsList: ['Connect with other runners', 'Training tips', 'Race day updates'],
16796      intendedAction: 'going',
16797      intendedData: { raceData: race },
16798      ...config
16799    });
16800  },
16801
16802  setPriceAlert: function(race, config = {}) {
16803    window.AuthModal.show({
16804      context: {
16805        icon: '💰',
16806        title: race.name,
16807        subtitle: `Current price: ${race.price || 'TBD'}`
16808      },
16809      title: 'Get notified when prices drop',
16810      subtitle: 'Never miss a deal',
16811      benefits: true,
16812      benefitsList: ['Price drop alerts', 'Early bird notifications', 'Discount codes'],
16813      intendedAction: 'alert',
16814      intendedData: { raceData: race },
16815      ...config
16816    });
16817  },
16818
16819  joinDiscussion: function(topic, config = {}) {
16820    window.AuthModal.show({
16821      context: {
16822        icon: '💬',
16823        title: topic.title || 'Discussion',
16824        subtitle: topic.subtitle || 'Join the conversation'
16825      },
16826      title: 'Join the discussion',
16827      subtitle: 'Share your thoughts with fellow runners',
16828      benefits: true,
16829      benefitsList: ['Connect with runners', 'Get expert advice', 'Share experiences'],
16830      intendedAction: 'comment',
16831      intendedData: { topicData: topic },
16832      ...config
16833    });
16834  },
16835
16836  useCommunityFilters: function(config = {}) {
16837    const { filterType } = config;
16838    window.AuthModal.show({
16839      context: {
16840        icon: '👥',
16841        title: 'Community Filters',
16842        subtitle: 'Track races your friends are attending'
16843      },
16844      title: 'Join the RaceMob Community',
16845      subtitle: 'See what races your friends are running',
16846      benefits: true,
16847      benefitsList: [
16848        'See friends\' upcoming races',
16849        'Save unlimited races',
16850        'Get price drop alerts',
16851        'Track your race history'
16852      ],
16853      intendedAction: 'use_community_filter',
16854      intendedData: { filterType: filterType },
16855      ...config
16856    });
16857  },
16858
16859  // Utility methods
16860  isOpen: function() {
16861    return window.AuthModal.isOpen;
16862  },
16863
16864  close: function() {
16865    window.AuthModal.hide();
16866  },
16867
16868  // Event listeners for automatic modal triggering
16869  initAutoTriggers: function() {
16870    // Auto-trigger on auth-required elements
16871    document.addEventListener('click', function(e) {
16872      const authRequired = e.target.closest('[data-auth-required]');
16873      if (authRequired && !window.isUserSignedIn) {
16874        e.preventDefault();
16875        const config = authRequired.dataset.authConfig ? 
16876                      JSON.parse(authRequired.dataset.authConfig) : {};
16877        window.AuthModalAPI.signup(config);
16878      }
16879    });
16880
16881    // Auto-trigger on race action buttons
16882    document.addEventListener('click', function(e) {
16883      const raceBtn = e.target.closest('[data-race-action]');
16884      if (raceBtn && !window.isUserSignedIn) {
16885        e.preventDefault();
16886        const action = raceBtn.dataset.raceAction;
16887        const raceData = JSON.parse(raceBtn.dataset.raceData || '{}');
16888        
16889        switch(action) {
16890          case 'save':
16891            window.AuthModalAPI.saveRace(raceData);
16892            break;
16893          case 'going':
16894            window.AuthModalAPI.markGoing(raceData);
16895            break;
16896          case 'alert':
16897            window.AuthModalAPI.setPriceAlert(raceData);
16898            break;
16899          default:
16900            window.AuthModalAPI.signup();
16901        }
16902      }
16903    });
16904  },
16905
16906  // Configuration helpers
16907  createRaceConfig: function(race) {
16908    return {
16909      context: {
16910        icon: '🏃',
16911        title: race.name,
16912        subtitle: `${race.date} • ${race.location}`
16913      }
16914    };
16915  },
16916
16917  createTopicConfig: function(topic) {
16918    return {
16919      context: {
16920        icon: '💬',
16921        title: topic.title,
16922        subtitle: topic.description
16923      }
16924    };
16925  }
16926};
16927
16928// Initialize auto-triggers when DOM is ready
16929document.addEventListener('DOMContentLoaded', function() {
16930  window.AuthModalAPI.initAutoTriggers();
16931});
16932
16933// Global password visibility toggle function (used by auth modal and onboarding)
16934window.togglePassword = function(inputId) {
16935  const input = document.getElementById(inputId);
16936  if (!input) return;
16937
16938  const wrapper = input.parentElement;
16939  const eyeIcon = wrapper.querySelector('.eye-icon');
16940  const eyeOffIcon = wrapper.querySelector('.eye-off-icon');
16941
16942  if (input.type === 'password') {
16943    input.type = 'text';
16944    if (eyeIcon) eyeIcon.style.display = 'none';
16945    if (eyeOffIcon) eyeOffIcon.style.display = 'block';
16946  } else {
16947    input.type = 'password';
16948    if (eyeIcon) eyeIcon.style.display = 'block';
16949    if (eyeOffIcon) eyeOffIcon.style.display = 'none';
16950  }
16951};
16952
16953// Export for module systems
16954if (typeof module !== 'undefined' && module.exports) {
16955  module.exports = AuthModal;
16956}
16957
16958})();
16959
16960// auth_modal_compat.js
16961//
16962// Compat shim: restores the legacy `window.showDialogLink` global on layouts
16963// whose page bundle does not require functions.js (the file that historically
16964// defined this helper).
16965//
16966// Why this exists:
16967//   c5a144af3 introduced /onboarding/start which natively POSTs /login on
16968//   failed sign-in. 009b1e207 then split JS into a base bundle that did not
16969//   include functions.js. After a failed login on those layouts, the server
16970//   sets session[:init_popup_link]="/login" and the layout footer emits an
16971//   inline `setTimeout(function() { showDialogLink("/login", ""); }, ...)`.
16972//   Without functions.js loaded, that throws ReferenceError and the auth
16973//   modal never reopens. See .planning/debug/auth-modal-architecture.md.
16974//
16975// Scope:
16976//   This shim handles AUTH urls only (login / signup paths). Non-auth
16977//   showDialogLink callsites (subscriptions popups, suggested race edit,
16978//   etc.) still depend on the original functions.js implementation and
16979//   must continue to ship in those page bundles. A Sentry breadcrumb is
16980//   recorded if a non-auth url reaches this shim so we can quantify gap.
16981//
16982// Idempotency:
16983//   The shim is a no-op when functions.js has already defined
16984//   `window.showDialogLink`, so it is safe to load on every layout —
16985//   including ones that already bundle functions.js.
16986(function () {
16987  if (typeof window.showDialogLink === 'function') {
16988    return;
16989  }
16990
16991  function classifyAuthUrl(link) {
16992    if (typeof link !== 'string' || link.length === 0) {
16993      return null;
16994    }
16995    var url;
16996    try {
16997      url = new URL(link, window.location.origin);
16998    } catch (e) {
16999      return null;
17000    }
17001    if (url.origin !== window.location.origin) {
17002      return null;
17003    }
17004    var path = url.pathname;
17005    if (/^\/(login|sign_in|sessions)(\/|$)/.test(path)) {
17006      return 'signin';
17007    }
17008    if (/^\/(registrations|sign_up|onboarding|subscriptions)(\/|$)/.test(path)) {
17009      // /subscriptions/grassroots is set as init_popup_link by
17010      // subscriptions_controller#index — treat as a signup intent so the
17011      // modal opens on the signup tab rather than the shim's fallback
17012      // window.location.href redirect.
17013      return 'signup';
17014    }
17015    return null;
17016  }
17017
17018  window.showDialogLink = function (url, _formParams) {
17019    var tab = classifyAuthUrl(url);
17020
17021    if (!tab) {
17022      if (window.Sentry && typeof window.Sentry.addBreadcrumb === 'function') {
17023        window.Sentry.addBreadcrumb({
17024          category: 'auth-modal-compat',
17025          message: 'showDialogLink shim received non-auth URL (functions.js not loaded on this layout)',
17026          level: 'warning',
17027          data: { url: String(url).slice(0, 200) }
17028        });
17029      }
17030      if (window.console && typeof window.console.warn === 'function') {
17031        window.console.warn('[auth_modal_compat] non-auth showDialogLink URL; functions.js not loaded:', url);
17032      }
17033      return;
17034    }
17035
17036    if (window.AuthModal && typeof window.AuthModal.show === 'function') {
17037      window.AuthModal.show({
17038        defaultTab: tab,
17039        context: {
17040          icon: '🏃',
17041          title: 'Welcome to RaceMob',
17042          subtitle: tab === 'signin' ? 'Sign in to continue' : 'Join our community of runners'
17043        }
17044      });
17045      return;
17046    }
17047
17048    window.location.href = tab === 'signin' ? '/onboarding/start#signin' : '/onboarding/start';
17049  };
17050})();
17051document.addEventListener('DOMContentLoaded', function() {
17052  const mobileMenuToggle = document.querySelector('.mobile-menu-toggle');
17053  const mobileMenu = document.getElementById('mobileMenu');
17054  const closeBtn = document.querySelector('.close-btn');
17055  let menuOpen = false;
17056
17057  // Toggle mobile menu
17058  function toggleMenu() {
17059    menuOpen = !menuOpen;
17060    
17061    if (menuOpen) {
17062      mobileMenu.classList.add('active');
17063      mobileMenuToggle.classList.add('active');
17064      document.body.style.overflow = 'hidden';
17065    } else {
17066      mobileMenu.classList.remove('active');
17067      mobileMenuToggle.classList.remove('active');
17068      document.body.style.overflow = 'auto';
17069    }
17070  }
17071
17072  // Event listeners
17073  if (mobileMenuToggle) {
17074    mobileMenuToggle.addEventListener('click', toggleMenu);
17075  }
17076
17077  if (closeBtn) {
17078    closeBtn.addEventListener('click', toggleMenu);
17079  }
17080
17081  // Close menu on escape key
17082  document.addEventListener('keydown', function(e) {
17083    if (e.key === 'Escape' && menuOpen) {
17084      toggleMenu();
17085    }
17086  });
17087
17088  // Close menu when clicking outside
17089  document.addEventListener('click', function(e) {
17090    if (menuOpen && 
17091        !e.target.closest('.mobile-menu') && 
17092        !e.target.closest('.mobile-menu-toggle')) {
17093      toggleMenu();
17094    }
17095  });
17096
17097  // Handle dropdown menus on mobile (touch devices)
17098  const dropdownTriggers = document.querySelectorAll('.has-dropdown');
17099
17100  dropdownTriggers.forEach(trigger => {
17101    trigger.addEventListener('click', function(e) {
17102      if (window.innerWidth <= 768) {
17103        e.preventDefault();
17104        const dropdown = this.nextElementSibling;
17105        dropdown.classList.toggle('active');
17106      }
17107    });
17108  });
17109
17110  // Handle mobile accordion menus
17111  const accordionTriggers = document.querySelectorAll('.nav-accordion-trigger');
17112
17113  accordionTriggers.forEach(trigger => {
17114    trigger.addEventListener('click', function(e) {
17115      e.preventDefault();
17116      const accordion = this.closest('.nav-accordion');
17117
17118      // Close other accordions (optional - remove if you want multiple open)
17119      const allAccordions = document.querySelectorAll('.nav-accordion');
17120      allAccordions.forEach(acc => {
17121        if (acc !== accordion) {
17122          acc.classList.remove('active');
17123        }
17124      });
17125
17126      // Toggle current accordion
17127      accordion.classList.toggle('active');
17128    });
17129  });
17130
17131  // Add active class to current page nav item
17132  const currentPath = window.location.pathname;
17133  const navLinks = document.querySelectorAll('.nav-link, .nav-link-mobile');
17134  
17135  navLinks.forEach(link => {
17136    if (link.getAttribute('href') === currentPath) {
17137      link.classList.add('active');
17138    }
17139  });
17140});
17141
17142// Turbo/Turbolinks compatibility for Rails
17143document.addEventListener('turbo:load', function() {
17144  // Re-initialize navigation after Turbo navigation
17145  const event = new Event('DOMContentLoaded');
17146  document.dispatchEvent(event);
17147});
17148
17149// ============================================================================
17150// Email Verification Banner
17151// ============================================================================
17152
17153document.addEventListener('DOMContentLoaded', function() {
17154  initVerificationBanner();
17155});
17156
17157function initVerificationBanner() {
17158  const banner = document.getElementById('verification-banner');
17159  if (!banner) return;
17160
17161  // Check session storage to hide banner if dismissed
17162  if (sessionStorage.getItem('verification_banner_dismissed') === 'true') {
17163    banner.style.display = 'none';
17164    return;
17165  }
17166
17167  // Dismiss banner buttons
17168  const dismissButtons = document.querySelectorAll('#dismiss-banner-btn, #close-banner-btn');
17169  dismissButtons.forEach(button => {
17170    button.addEventListener('click', function(e) {
17171      e.preventDefault();
17172      dismissVerificationBanner();
17173    });
17174  });
17175
17176  // Resend verification email button
17177  const resendButton = document.getElementById('resend-verification-btn');
17178  if (resendButton) {
17179    resendButton.addEventListener('click', function(e) {
17180      e.preventDefault();
17181      console.log('Resend verification button clicked');
17182      resendVerificationEmail();
17183    });
17184  }
17185}
17186
17187function dismissVerificationBanner() {
17188  const banner = document.getElementById('verification-banner');
17189  if (!banner) return;
17190
17191  // Store dismissal in session storage
17192  sessionStorage.setItem('verification_banner_dismissed', 'true');
17193
17194  // Fade out banner
17195  banner.style.opacity = '0';
17196  banner.style.transition = 'opacity 300ms';
17197
17198  setTimeout(() => {
17199    banner.style.display = 'none';
17200  }, 300);
17201
17202  // Send request to server
17203  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
17204
17205  fetch('/users/dismiss_verification_banner', {
17206    method: 'POST',
17207    headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {},
17208    credentials: 'same-origin'
17209  })
17210  .then(response => {
17211    if (response.ok) {
17212      console.log('Verification banner dismissed');
17213    } else {
17214      throw new Error('Failed to dismiss banner');
17215    }
17216  })
17217  .catch(error => {
17218    console.error('Failed to dismiss banner:', error);
17219    // Show banner again if server request fails
17220    sessionStorage.removeItem('verification_banner_dismissed');
17221    banner.style.display = '';
17222    banner.style.opacity = '1';
17223  });
17224}
17225
17226function resendVerificationEmail() {
17227  console.log('resendVerificationEmail called');
17228
17229  const loadingElement = document.getElementById('verification-banner-loading');
17230  const contentElement = document.querySelector('.verification-banner-content');
17231
17232  if (!loadingElement || !contentElement) {
17233    console.error('Loading or content element not found');
17234    return;
17235  }
17236
17237  console.log('Showing loading state');
17238
17239  // Show loading state
17240  contentElement.style.display = 'none';
17241  loadingElement.style.display = 'block';
17242
17243  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
17244
17245  console.log('Sending POST request to /user/resend_verification');
17246
17247  fetch('/user/resend_verification', {
17248    method: 'POST',
17249    headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {},
17250    credentials: 'same-origin'
17251  })
17252  .then(response => {
17253    if (!response.ok) {
17254      return response.json().then(data => {
17255        throw new Error(data.error || 'Failed to send verification email');
17256      });
17257    }
17258    return response.json();
17259  })
17260  .then(data => {
17261    console.log('Resend verification success:', data);
17262    showSuccessMessage('Verification email sent! Please check your inbox.');
17263
17264    // Hide banner after successful resend
17265    setTimeout(() => {
17266      const banner = document.getElementById('verification-banner');
17267      if (banner) {
17268        banner.style.opacity = '0';
17269        banner.style.transition = 'opacity 300ms';
17270        setTimeout(() => banner.style.display = 'none', 300);
17271      }
17272    }, 2000);
17273  })
17274  .catch(error => {
17275    console.error('Resend verification failed:', error);
17276    showErrorMessage(error.message || 'Failed to send verification email. Please try again.');
17277  })
17278  .finally(() => {
17279    console.log('Resend verification request completed');
17280    // Hide loading state
17281    loadingElement.style.display = 'none';
17282    contentElement.style.display = '';
17283  });
17284}
17285
17286function showSuccessMessage(message) {
17287  const messageDiv = document.createElement('div');
17288  messageDiv.className = 'verification-success-message';
17289  messageDiv.textContent = message;
17290  messageDiv.style.cssText = `
17291    position: fixed;
17292    top: 80px;
17293    right: 20px;
17294    background: #d4edda;
17295    color: #155724;
17296    padding: 12px 16px;
17297    border: 1px solid #c3e6cb;
17298    border-radius: 4px;
17299    z-index: 10000;
17300    max-width: 400px;
17301    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
17302    opacity: 0;
17303    transition: opacity 300ms;
17304  `;
17305
17306  document.body.appendChild(messageDiv);
17307
17308  // Fade in
17309  setTimeout(() => messageDiv.style.opacity = '1', 10);
17310
17311  // Remove after 5 seconds
17312  setTimeout(() => {
17313    messageDiv.style.opacity = '0';
17314    setTimeout(() => messageDiv.remove(), 300);
17315  }, 5000);
17316}
17317
17318function showErrorMessage(message) {
17319  const messageDiv = document.createElement('div');
17320  messageDiv.className = 'verification-error-message';
17321  messageDiv.textContent = message;
17322  messageDiv.style.cssText = `
17323    position: fixed;
17324    top: 80px;
17325    right: 20px;
17326    background: #f8d7da;
17327    color: #721c24;
17328    padding: 12px 16px;
17329    border: 1px solid #f5c6cb;
17330    border-radius: 4px;
17331    z-index: 10000;
17332    max-width: 400px;
17333    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
17334    opacity: 0;
17335    transition: opacity 300ms;
17336  `;
17337
17338  document.body.appendChild(messageDiv);
17339
17340  // Fade in
17341  setTimeout(() => messageDiv.style.opacity = '1', 10);
17342
17343  // Remove after 5 seconds
17344  setTimeout(() => {
17345    messageDiv.style.opacity = '0';
17346    setTimeout(() => messageDiv.remove(), 300);
17347  }, 5000);
17348}
17349// ============================================================================
17350// Email Verification Modal and Action Gates
17351// ============================================================================
17352
17353document.addEventListener('DOMContentLoaded', function() {
17354  initVerificationModal();
17355  setupVerificationActionGates();
17356});
17357
17358function initVerificationModal() {
17359  const modal = document.getElementById('verification-modal');
17360  if (!modal) return;
17361
17362  // Close modal buttons
17363  const closeButtons = document.querySelectorAll('#close-modal-btn, #modal-later-btn');
17364  closeButtons.forEach(button => {
17365    button.addEventListener('click', function(e) {
17366      e.preventDefault();
17367      closeVerificationModal();
17368    });
17369  });
17370
17371  // Backdrop dismissal, Escape, the focus trap and the scroll lock are all the
17372  // chassis's now (components/modal.js). The hand-rolled overlay-click handler that
17373  // used to live here bound to .verification-modal-overlay, an element the chassis
17374  // markup does not have.
17375
17376  // Resend verification email from modal
17377  const modalResendButton = document.getElementById('modal-resend-verification-btn');
17378  if (modalResendButton) {
17379    modalResendButton.addEventListener('click', function(e) {
17380      e.preventDefault();
17381      resendVerificationEmailFromModal();
17382    });
17383  }
17384
17385  // (The click-stop that used to guard .verification-modal-content is gone with it —
17386  // the chassis only dismisses on the overlay itself, never on a click inside the
17387  // dialog.)
17388}
17389
17390// Guard to prevent duplicate listener registration
17391var verificationGatesSetup = false;
17392
17393function setupVerificationActionGates() {
17394  // Only setup once to prevent duplicate listeners
17395  if (verificationGatesSetup) return;
17396  verificationGatesSetup = true;
17397
17398  // Handle AJAX errors for verification-required actions
17399  // When backend returns 403, assume it's a verification requirement
17400  document.addEventListener('ajax:error', function(event) {
17401    const xhr = event.detail[2];
17402
17403    // Check if response is 403 (forbidden) - this means verification required
17404    if (xhr && xhr.status === 403) {
17405      const element = event.target;
17406      const actionText = element.getAttribute('data-action-text') || 'follow other users';
17407      showVerificationModal(actionText);
17408    }
17409  });
17410}
17411
17412function showVerificationModal(actionText) {
17413  const modal = document.getElementById('verification-modal');
17414  if (!modal) return;
17415
17416  // Update action text
17417  const actionTextElement = document.getElementById('verification-action-text');
17418  if (actionTextElement) {
17419    actionTextElement.textContent = actionText;
17420  }
17421
17422  // On the rm-modal chassis (components/modal.js). Hand-rolling the open here used to
17423  // mean no focus trap, no Escape, and a scroll lock that set body's overflow only —
17424  // which does nothing on this site, because responsive.css.scss:269 gives <html>
17425  // overflow-y:auto and the browser then stops propagating body's overflow to the
17426  // viewport. The chassis locks both. Fallback keeps the modal usable if base.js has
17427  // not run, the same shape teams/gallery.js uses.
17428  if (window.RmModal && window.RmModal.open) {
17429    window.RmModal.open(modal);
17430    return;
17431  }
17432  modal.style.display = 'flex';
17433  modal.style.opacity = '1';
17434}
17435
17436function closeVerificationModal() {
17437  const modal = document.getElementById('verification-modal');
17438  if (!modal) return;
17439
17440  if (window.RmModal && window.RmModal.close) {
17441    window.RmModal.close(modal);
17442    return;
17443  }
17444  modal.style.display = 'none';
17445  modal.style.opacity = '';
17446}
17447
17448function needsVerification() {
17449  // Check if current user needs verification by checking if modal exists
17450  return document.getElementById('verification-modal') !== null;
17451}
17452
17453function resendVerificationEmailFromModal() {
17454  const loadingElement = document.getElementById('verification-modal-loading');
17455  // Was `.verification-modal-content > :not(.verification-modal-loading)`, a wrapper
17456  // the chassis markup does not have — the spinner now lives inside .rm-modal__body,
17457  // so the thing to hide while sending is that body's other children.
17458  const modalEl = document.getElementById('verification-modal');
17459  const contentElement = modalEl
17460    ? modalEl.querySelector('.rm-modal__body')
17461    : null;
17462
17463  if (!loadingElement) return;
17464
17465  // Hide the body's copy, not the body itself — the spinner is inside it.
17466  if (contentElement) {
17467    Array.prototype.forEach.call(contentElement.children, function (child) {
17468      if (child !== loadingElement) child.style.display = 'none';
17469    });
17470  }
17471  loadingElement.style.display = 'block';
17472
17473  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
17474
17475  fetch('/user/resend_verification', {
17476    method: 'POST',
17477    headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {},
17478    credentials: 'same-origin'
17479  })
17480  .then(response => {
17481    if (!response.ok) {
17482      return response.json().then(data => {
17483        throw new Error(data.error || 'Failed to send verification email');
17484      });
17485    }
17486    return response.json();
17487  })
17488  .then(data => {
17489    showSuccessMessage('Verification email sent! Please check your inbox.');
17490    closeVerificationModal();
17491  })
17492  .catch(error => {
17493    showErrorMessage(error.message || 'Failed to send verification email. Please try again.');
17494  })
17495  .finally(() => {
17496    loadingElement.style.display = 'none';
17497    if (contentElement) {
17498      Array.prototype.forEach.call(contentElement.children, function (child) {
17499        if (child !== loadingElement) child.style.display = '';
17500      });
17501    }
17502  });
17503}
17504
17505// Utility function to add verification requirements to specific elements dynamically
17506window.addVerificationRequirement = function(selector, actionText) {
17507  const elements = document.querySelectorAll(selector);
17508  elements.forEach(element => {
17509    element.classList.add('requires-verification');
17510    element.setAttribute('data-action-text', actionText);
17511  });
17512  // Re-setup action gates for newly added elements
17513  setupVerificationActionGates();
17514};
17515/* rm-sitenav (E-nav): avatar account menu + mobile drawer.
17516   Vanilla, Turbolinks-safe, idempotent — delegates off `document` so a single
17517   bind survives page/Turbolinks swaps without re-attaching to fresh nav nodes. */
17518
17519(function () {
17520  if (window.__rmSitenavBound) return;
17521  window.__rmSitenavBound = true;
17522
17523  function el(id) { return document.getElementById(id); }
17524
17525  function closeMenu() {
17526    var m = el('rm-nav-avatar-menu');
17527    if (m) m.classList.remove('is-open');
17528    var b = el('rm-nav-avatar-btn');
17529    if (b) b.setAttribute('aria-expanded', 'false');
17530  }
17531
17532  function openDrawer() {
17533    var d = el('rm-nav-drawer'), s = el('rm-nav-scrim');
17534    if (d) d.classList.add('is-open');
17535    if (s) s.classList.add('is-open');
17536    document.body.style.overflow = 'hidden';
17537  }
17538
17539  function closeDrawer() {
17540    var d = el('rm-nav-drawer'), s = el('rm-nav-scrim');
17541    if (d) d.classList.remove('is-open');
17542    if (s) s.classList.remove('is-open');
17543    document.body.style.overflow = '';
17544  }
17545
17546  document.addEventListener('click', function (e) {
17547    var t = e.target;
17548    if (!t || !t.closest) return;
17549
17550    if (t.closest('#rm-nav-avatar-btn')) {
17551      e.stopPropagation();
17552      var m = el('rm-nav-avatar-menu'), b = el('rm-nav-avatar-btn');
17553      if (m) {
17554        var open = m.classList.toggle('is-open');
17555        if (b) b.setAttribute('aria-expanded', open ? 'true' : 'false');
17556      }
17557      return;
17558    }
17559    if (!t.closest('#rm-nav-avatar-menu')) closeMenu();
17560
17561    if (t.closest('#rm-nav-burger')) { openDrawer(); return; }
17562    if (t.closest('#rm-nav-drawer-close') || t.closest('#rm-nav-scrim')) { closeDrawer(); }
17563  });
17564
17565  document.addEventListener('keydown', function (e) {
17566    if (e.key === 'Escape') { closeMenu(); closeDrawer(); }
17567  });
17568})();
17569/**
17570 * Podcast Mini-Player Persistence
17571 *
17572 * Keeps #racemob-player-container alive across Turbolinks navigations,
17573 * even when navigating between pages that use different layouts.
17574 *
17575 * Loaded via base.js (universal) so it works on every page.
17576 * The full player logic lives in podcast_player.js (learning_bundle only).
17577 */
17578
17579(function() {
17580  'use strict';
17581
17582  // Before Turbolinks swaps the body, transfer the live player container
17583  // (with its audio element, DOM, and event listeners) into the incoming body.
17584  document.addEventListener('turbolinks:before-render', function(event) {
17585    var container = document.getElementById('racemob-player-container');
17586    if (!container) return;
17587
17588    var newBody = event.data.newBody;
17589
17590    // Remove any empty placeholder container in the new page (avoids duplicate IDs)
17591    var placeholder = newBody.querySelector('#racemob-player-container');
17592    if (placeholder) placeholder.parentNode.removeChild(placeholder);
17593
17594    // Transplant the live container into the new body
17595    newBody.appendChild(container);
17596  });
17597})();
17598/**
17599 * RaceMob Persistent Podcast Mini-Player
17600 *
17601 * Spotify-style sticky bottom bar that persists across Turbolinks page navigation.
17602 * Lives outside the Turbolinks-replaced body in #racemob-player-container.
17603 *
17604 * Features:
17605 * - Single <audio> element reused across episodes
17606 * - Progress bar with chapter marker dots and click-to-seek
17607 * - Playback speed toggle (1x / 1.25x / 1.5x / 2x)
17608 * - LocalStorage persistence of last-played episode + position + speed
17609 * - Syncs with inline episode page player when on same episode
17610 * - Dispatches timeupdate events for transcript sync
17611 * - Mobile-responsive collapsed layout
17612 */
17613
17614(function() {
17615  'use strict';
17616
17617  var STORAGE_KEY = 'racemob_player_state';
17618
17619  // Inline SVG icons for the mini-player (no Font Awesome dependency)
17620  var SVG_PLAY = '<svg viewBox="0 0 384 512" aria-hidden="true"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.8 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>';
17621  var SVG_PAUSE = '<svg viewBox="0 0 320 512" aria-hidden="true"><path d="M48 64C21.5 64 0 85.5 0 112V400c0 26.5 21.5 48 48 48H80c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H48zm192 0c-26.5 0-48 21.5-48 48V400c0 26.5 21.5 48 48 48h32c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H240z"/></svg>';
17622  var SVG_XMARK = '<svg viewBox="0 0 384 512" aria-hidden="true"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5-12.5 32.8-12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>';
17623  var SVG_CHEVRON_UP = '<svg viewBox="0 0 512 512" aria-hidden="true"><path d="M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"/></svg>';
17624  var SVG_CHEVRON_DOWN = '<svg viewBox="0 0 512 512" aria-hidden="true"><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>';
17625  var SVG_PODCAST = '<svg viewBox="0 0 448 512" aria-hidden="true"><path d="M319.4 372c48.5-31.3 80.6-86 80.6-148C400 100.3 315.7 16 224 16S48 100.3 48 224c0 62 32.1 116.7 80.6 148c1.2-17.2 4-38 8.1-55.1C109.4 295.6 96 261.9 96 224c0-70.7 57.3-128 128-128s128 57.3 128 128c0 37.9-13.4 71.6-40.7 92.9c4.1 17.1 6.9 37.9 8.1 55.1zm-76.5 31.7C252.3 397.6 247.5 416 224 448c-23.5-32-28.3-50.4-18.9-44.3C170.4 388.5 144 309.8 144 224c0-44.2 35.8-80 80-80s80 35.8 80 80c0 85.8-26.4 164.5-61.1 179.7zM224 256a32 32 0 1 0 0-64 32 32 0 1 0 0 64zm0 224a24 24 0 1 1 0-48 24 24 0 1 1 0 48z"/></svg>';
17626
17627  function isSafeAudioUrl(url) {
17628    if (typeof url !== 'string' || !url.length) return false;
17629    // Accept absolute URLs and relative paths (e.g. /uploads/... from CarrierWave in dev)
17630    return /^https?:\/\//i.test(url) || /^\/[^\/]/.test(url);
17631  }
17632
17633  function safePlay(audioEl) {
17634    var p = audioEl.play();
17635    if (p && typeof p.catch === 'function') {
17636      p.catch(function(err) {
17637        if (err.name !== 'AbortError') {
17638          console.warn('RaceMobPlayer: play() failed', err);
17639        }
17640      });
17641    }
17642  }
17643
17644  var RaceMobPlayer = {
17645    audio: null,
17646    currentEpisode: null,
17647    playerBar: null,
17648    container: null,
17649    speeds: [1, 1.25, 1.5, 2],
17650    currentSpeedIndex: 0,
17651    _initialized: false,
17652    _isSeeking: false,
17653    _saveDebounce: null,
17654    _mobileExpanded: false,
17655    _seekOnLoadHandler: null,
17656
17657    // ========================================================================
17658    // INITIALIZATION
17659    // ========================================================================
17660
17661    init: function() {
17662      this.container = document.getElementById('racemob-player-container');
17663
17664      // Auto-create container on pages that don't have it in their layout
17665      if (!this.container) {
17666        this.container = document.createElement('div');
17667        this.container.id = 'racemob-player-container';
17668        document.body.appendChild(this.container);
17669      }
17670
17671      // Ensure Font Awesome is available (some layouts don't include it)
17672      this._ensureFontAwesome();
17673
17674      // Already initialized (Turbolinks navigation) — just re-bind page elements
17675      if (this._initialized) {
17676        if (this.playerBar && this.playerBar.classList.contains('mini-player--visible')) {
17677          document.body.classList.add('has-mini-player');
17678        }
17679        this.bindPageElements();
17680        this.syncWithPagePlayer();
17681        return;
17682      }
17683
17684      // Create the audio element once
17685      if (!this.audio) {
17686        this.audio = document.createElement('audio');
17687        this.audio.preload = 'metadata';
17688        this.container.appendChild(this.audio);
17689
17690        // Audio event listeners
17691        this.audio.addEventListener('timeupdate', this._onTimeUpdate.bind(this));
17692        this.audio.addEventListener('loadedmetadata', this._onLoadedMetadata.bind(this));
17693        this.audio.addEventListener('ended', this._onEnded.bind(this));
17694        this.audio.addEventListener('play', this._onPlayStateChange.bind(this));
17695        this.audio.addEventListener('pause', this._onPlayStateChange.bind(this));
17696        this.audio.addEventListener('error', this._onAudioError.bind(this));
17697      }
17698
17699      // Build the player bar DOM (hidden until an episode is loaded)
17700      this._buildPlayerBar();
17701
17702      // Restore previous state from LocalStorage
17703      this.restoreState();
17704
17705      // Bind page elements
17706      this.bindPageElements();
17707
17708      this._initialized = true;
17709    },
17710
17711    // ========================================================================
17712    // PLAYER BAR DOM
17713    // ========================================================================
17714
17715    _buildPlayerBar: function() {
17716      // Don't rebuild if already exists
17717      if (this.playerBar) return;
17718
17719      var bar = document.createElement('div');
17720      bar.className = 'mini-player';
17721      bar.setAttribute('role', 'region');
17722      bar.setAttribute('aria-label', 'Audio player');
17723      bar.innerHTML =
17724        '<div class="mini-player__inner">' +
17725          // Left: thumbnail + play/pause
17726          '<div class="mini-player__left">' +
17727            '<div class="mini-player__thumb">' +
17728              '<img alt="" class="mini-player__thumb-img" style="display:none;">' +
17729              '<div class="mini-player__thumb-placeholder">' + SVG_PODCAST + '</div>' +
17730            '</div>' +
17731            '<button type="button" class="mini-player__play-btn" aria-label="Play">' +
17732              SVG_PLAY +
17733            '</button>' +
17734          '</div>' +
17735          // Center: title, series, progress bar
17736          '<div class="mini-player__center">' +
17737            '<div class="mini-player__info">' +
17738              '<a href="#" class="mini-player__title"></a>' +
17739              '<span class="mini-player__series"></span>' +
17740            '</div>' +
17741            '<div class="mini-player__progress">' +
17742              '<div class="mini-player__bar" role="slider" aria-label="Seek" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" tabindex="0">' +
17743                '<div class="mini-player__bar-fill"></div>' +
17744                '<div class="mini-player__bar-chapters"></div>' +
17745              '</div>' +
17746            '</div>' +
17747          '</div>' +
17748          // Right: time, speed, close
17749          '<div class="mini-player__right">' +
17750            '<span class="mini-player__time">' +
17751              '<span class="mini-player__time-current">0:00</span>' +
17752              ' / ' +
17753              '<span class="mini-player__time-total">0:00</span>' +
17754            '</span>' +
17755            '<button type="button" class="mini-player__speed-btn" aria-label="Playback speed">1x</button>' +
17756            '<button type="button" class="mini-player__close-btn" aria-label="Close player">' +
17757              SVG_XMARK +
17758            '</button>' +
17759          '</div>' +
17760          // Mobile expand area (tap to expand)
17761          '<button type="button" class="mini-player__mobile-expand" aria-label="Expand player">' +
17762            SVG_CHEVRON_UP +
17763          '</button>' +
17764          // Screen reader live region for state announcements
17765          '<span class="visually-hidden mini-player__sr-status" aria-live="polite" aria-atomic="true"></span>' +
17766        '</div>';
17767
17768      this.container.appendChild(bar);
17769      this.playerBar = bar;
17770
17771      // Bind player bar controls
17772      this._bindPlayerBarEvents();
17773    },
17774
17775    _bindPlayerBarEvents: function() {
17776      var self = this;
17777      var bar = this.playerBar;
17778
17779      // Play/pause button
17780      var playBtn = bar.querySelector('.mini-player__play-btn');
17781      if (playBtn) {
17782        playBtn.addEventListener('click', function(e) {
17783          e.preventDefault();
17784          e.stopPropagation();
17785          self.toggle();
17786        });
17787      }
17788
17789      // Progress bar click-to-seek
17790      var progressBar = bar.querySelector('.mini-player__bar');
17791      if (progressBar) {
17792        progressBar.addEventListener('click', function(e) {
17793          if (!self.audio || !self.audio.duration) return;
17794          var rect = progressBar.getBoundingClientRect();
17795          var pct = (e.clientX - rect.left) / rect.width;
17796          pct = Math.max(0, Math.min(1, pct));
17797          self.audio.currentTime = pct * self.audio.duration;
17798          self.updateProgress();
17799        });
17800
17801        // Keyboard seek on progress bar
17802        progressBar.addEventListener('keydown', function(e) {
17803          if (!self.audio || !self.audio.duration) return;
17804          if (e.key === 'ArrowRight') {
17805            self.audio.currentTime = Math.min(self.audio.duration, self.audio.currentTime + 10);
17806            e.preventDefault();
17807          } else if (e.key === 'ArrowLeft') {
17808            self.audio.currentTime = Math.max(0, self.audio.currentTime - 10);
17809            e.preventDefault();
17810          }
17811        });
17812      }
17813
17814      // Speed toggle
17815      var speedBtn = bar.querySelector('.mini-player__speed-btn');
17816      if (speedBtn) {
17817        speedBtn.addEventListener('click', function(e) {
17818          e.preventDefault();
17819          e.stopPropagation();
17820          self.cycleSpeed();
17821        });
17822      }
17823
17824      // Close button
17825      var closeBtn = bar.querySelector('.mini-player__close-btn');
17826      if (closeBtn) {
17827        closeBtn.addEventListener('click', function(e) {
17828          e.preventDefault();
17829          e.stopPropagation();
17830          self.hide();
17831        });
17832      }
17833
17834      // Mobile expand toggle
17835      var expandBtn = bar.querySelector('.mini-player__mobile-expand');
17836      if (expandBtn) {
17837        expandBtn.addEventListener('click', function(e) {
17838          e.preventDefault();
17839          e.stopPropagation();
17840          self._toggleMobileExpand();
17841        });
17842      }
17843
17844      // Title link click
17845      var titleLink = bar.querySelector('.mini-player__title');
17846      if (titleLink) {
17847        titleLink.addEventListener('click', function(e) {
17848          // Allow normal link behavior (Turbolinks will handle)
17849        });
17850      }
17851    },
17852
17853    // ========================================================================
17854    // PLAYBACK CONTROLS
17855    // ========================================================================
17856
17857    play: function(episodeData) {
17858      if (!this.audio || !episodeData || !episodeData.audioUrl) return;
17859      if (!isSafeAudioUrl(episodeData.audioUrl)) return;
17860
17861      var isSameEpisode = this.currentEpisode &&
17862        this.currentEpisode.episodeId === episodeData.episodeId;
17863
17864      if (isSameEpisode) {
17865        // Same episode: just resume
17866        safePlay(this.audio);
17867      } else {
17868        // New episode: load and play
17869        this.currentEpisode = {
17870          episodeId: episodeData.episodeId,
17871          slug: episodeData.slug || '',
17872          title: episodeData.title || 'Untitled Episode',
17873          audioUrl: episodeData.audioUrl,
17874          thumbnail: episodeData.thumbnail || '',
17875          series: episodeData.series || '',
17876          episodeUrl: episodeData.episodeUrl || ''
17877        };
17878
17879        // Clean up any pending seek handler from previous load
17880        this._clearSeekOnLoad();
17881
17882        this.audio.src = episodeData.audioUrl;
17883
17884        // If we have a saved position, seek after metadata loads
17885        var saved = this._getSavedState();
17886        if (saved && saved.episodeId === episodeData.episodeId && saved.position > 0) {
17887          var targetPosition = saved.position;
17888          var audio = this.audio;
17889          var seekOnLoad = function() {
17890            if (targetPosition < audio.duration) {
17891              audio.currentTime = targetPosition;
17892            }
17893            audio.removeEventListener('loadedmetadata', seekOnLoad);
17894          };
17895          this._seekOnLoadHandler = seekOnLoad;
17896          this.audio.addEventListener('loadedmetadata', seekOnLoad);
17897        }
17898
17899        safePlay(this.audio);
17900      }
17901
17902      this._updatePlayerBarInfo();
17903      this.show();
17904      this.syncWithPagePlayer();
17905    },
17906
17907    pause: function() {
17908      if (!this.audio) return;
17909      this.audio.pause();
17910    },
17911
17912    toggle: function() {
17913      if (!this.audio) return;
17914
17915      if (this.audio.paused) {
17916        // If no source, try to restore
17917        if (!this.audio.src || this.audio.src === '') {
17918          this.restoreState();
17919          return;
17920        }
17921        safePlay(this.audio);
17922      } else {
17923        this.audio.pause();
17924      }
17925    },
17926
17927    seek: function(seconds) {
17928      if (!this.audio) return;
17929
17930      // If player has no source, try to restore first
17931      if (!this.audio.src || this.audio.src === '') {
17932        this.restoreState();
17933      }
17934
17935      seconds = parseFloat(seconds);
17936      if (isNaN(seconds)) return;
17937
17938      this.audio.currentTime = seconds;
17939
17940      // Start playing if not already
17941      if (this.audio.paused) {
17942        safePlay(this.audio);
17943      }
17944
17945      this.updateProgress();
17946      this.show();
17947    },
17948
17949    cycleSpeed: function() {
17950      this.currentSpeedIndex = (this.currentSpeedIndex + 1) % this.speeds.length;
17951      var speed = this.speeds[this.currentSpeedIndex];
17952
17953      if (this.audio) {
17954        this.audio.playbackRate = speed;
17955      }
17956
17957      // Update mini-player speed button
17958      var speedBtn = this.playerBar ? this.playerBar.querySelector('.mini-player__speed-btn') : null;
17959      if (speedBtn) {
17960        speedBtn.textContent = speed + 'x';
17961      }
17962
17963      // Update inline player speed button
17964      var pageSpeedBtn = document.querySelector('[data-player-speed]');
17965      if (pageSpeedBtn) {
17966        pageSpeedBtn.textContent = speed + 'x';
17967      }
17968
17969      this.saveState();
17970    },
17971
17972    // ========================================================================
17973    // PROGRESS AND TIME UPDATES
17974    // ========================================================================
17975
17976    updateProgress: function() {
17977      if (!this.audio || !this.audio.duration) return;
17978
17979      var currentTime = this.audio.currentTime;
17980      var duration = this.audio.duration;
17981      var pct = (currentTime / duration) * 100;
17982
17983      // Update mini-player bar
17984      if (this.playerBar) {
17985        var fill = this.playerBar.querySelector('.mini-player__bar-fill');
17986        if (fill) fill.style.width = pct + '%';
17987
17988        var slider = this.playerBar.querySelector('.mini-player__bar');
17989        if (slider) slider.setAttribute('aria-valuenow', Math.round(pct));
17990
17991        var timeCurrent = this.playerBar.querySelector('.mini-player__time-current');
17992        if (timeCurrent) timeCurrent.textContent = this.formatTime(currentTime);
17993
17994        var timeTotal = this.playerBar.querySelector('.mini-player__time-total');
17995        if (timeTotal) timeTotal.textContent = this.formatTime(duration);
17996      }
17997
17998      // Update inline page player if on the same episode page
17999      this._updatePagePlayer(currentTime, duration, pct);
18000    },
18001
18002    _updatePagePlayer: function(currentTime, duration, pct) {
18003      var pagePlayer = document.querySelector('[data-podcast-player]');
18004      if (!pagePlayer) return;
18005
18006      // Only update if it's the same episode
18007      var pageEpId = pagePlayer.getAttribute('data-episode-id');
18008      if (this.currentEpisode && pageEpId && pageEpId !== String(this.currentEpisode.episodeId)) return;
18009
18010      var pageFill = pagePlayer.querySelector('[data-player-fill]');
18011      if (pageFill) pageFill.style.width = pct + '%';
18012
18013      var pageCurrent = pagePlayer.querySelector('[data-player-current]');
18014      if (pageCurrent) pageCurrent.textContent = this.formatTime(currentTime);
18015
18016      var pageDuration = pagePlayer.querySelector('[data-player-duration]');
18017      if (pageDuration) pageDuration.textContent = this.formatTime(duration);
18018    },
18019
18020    // ========================================================================
18021    // AUDIO EVENT HANDLERS
18022    // ========================================================================
18023
18024    _onTimeUpdate: function() {
18025      this.updateProgress();
18026      this._debouncedSave();
18027
18028      // Dispatch custom event for transcript sync
18029      if (this.audio) {
18030        var event;
18031        try {
18032          event = new CustomEvent('racemob:timeupdate', {
18033            detail: {
18034              currentTime: this.audio.currentTime,
18035              duration: this.audio.duration,
18036              episodeId: this.currentEpisode ? this.currentEpisode.episodeId : null
18037            }
18038          });
18039        } catch (e) {
18040          // IE fallback
18041          event = document.createEvent('CustomEvent');
18042          event.initCustomEvent('racemob:timeupdate', true, true, {
18043            currentTime: this.audio.currentTime,
18044            duration: this.audio.duration,
18045            episodeId: this.currentEpisode ? this.currentEpisode.episodeId : null
18046          });
18047        }
18048        document.dispatchEvent(event);
18049      }
18050    },
18051
18052    _onLoadedMetadata: function() {
18053      this.updateProgress();
18054
18055      // Position chapter markers now that we know the duration
18056      this._updateChapterMarkers();
18057    },
18058
18059    _onEnded: function() {
18060      this._updatePlayButton(false);
18061      this.saveState();
18062    },
18063
18064    _onPlayStateChange: function() {
18065      var isPlaying = this.audio && !this.audio.paused;
18066      this._updatePlayButton(isPlaying);
18067      this.saveState();
18068    },
18069
18070    _onAudioError: function() {
18071      console.error('[PodcastPlayer] Audio load error:', this.audio.error);
18072      this._updatePlayButton(false);
18073      // Show error in the title area
18074      if (this.playerBar) {
18075        var titleEl = this.playerBar.querySelector('.mini-player__title');
18076        if (titleEl) titleEl.textContent = 'Unable to load audio';
18077      }
18078    },
18079
18080    _updatePlayButton: function(isPlaying) {
18081      // Update mini-player button (uses inline SVG — no Font Awesome needed)
18082      var miniBtn = this.playerBar ? this.playerBar.querySelector('.mini-player__play-btn') : null;
18083      if (miniBtn) {
18084        miniBtn.innerHTML = isPlaying ? SVG_PAUSE : SVG_PLAY;
18085        miniBtn.setAttribute('aria-label', isPlaying ? 'Pause' : 'Play');
18086      }
18087
18088      // Update inline page player button (only if it's the same episode)
18089      // Page player buttons use Font Awesome <i> tags (loaded by their own layouts)
18090      var pageBtn = document.querySelector('[data-player-play]');
18091      if (pageBtn) {
18092        var pagePlayer = document.querySelector('[data-podcast-player]');
18093        var pageEpId = pagePlayer ? pagePlayer.getAttribute('data-episode-id') : null;
18094        var isSamePage = this.currentEpisode && pageEpId &&
18095          String(pageEpId) === String(this.currentEpisode.episodeId);
18096
18097        if (isSamePage) {
18098          pageBtn.innerHTML = isPlaying
18099            ? '<i class="fa-solid fa-pause" aria-hidden="true"></i>'
18100            : '<i class="fa-solid fa-play" aria-hidden="true"></i>';
18101          pageBtn.setAttribute('aria-label', isPlaying ? 'Pause episode' : 'Play episode');
18102        } else {
18103          // Different episode on this page — always show play icon
18104          pageBtn.innerHTML = '<i class="fa-solid fa-play" aria-hidden="true"></i>';
18105          pageBtn.setAttribute('aria-label', 'Play episode');
18106        }
18107      }
18108
18109      // Announce state change to screen readers
18110      var srStatus = this.playerBar ? this.playerBar.querySelector('.mini-player__sr-status') : null;
18111      if (srStatus && this.currentEpisode) {
18112        srStatus.textContent = isPlaying
18113          ? 'Now playing: ' + this.currentEpisode.title
18114          : 'Paused: ' + this.currentEpisode.title;
18115      }
18116    },
18117
18118    // ========================================================================
18119    // CHAPTER MARKERS
18120    // ========================================================================
18121
18122    _updateChapterMarkers: function() {
18123      if (!this.playerBar || !this.audio || !this.audio.duration) return;
18124
18125      var chaptersContainer = this.playerBar.querySelector('.mini-player__bar-chapter
18125s');
18126      if (!chaptersContainer) return;
18127
18128      // Clear existing markers
18129      chaptersContainer.innerHTML = '';
18130
18131      // Read chapter markers from the inline player on the page (if present)
18132      var pagePlayer = document.querySelector('[data-podcast-player]');
18133      if (!pagePlayer) return;
18134
18135      var markers = pagePlayer.querySelectorAll('.podcast-ep-player__chapter-marker');
18136      var duration = this.audio.duration;
18137
18138      for (var i = 0; i < markers.length; i++) {
18139        var time = parseFloat(markers[i].getAttribute('data-time'));
18140        if (isNaN(time) || time <= 0) continue;
18141
18142        var pct = (time / duration) * 100;
18143        var dot = document.createElement('div');
18144        dot.className = 'mini-player__chapter-dot';
18145        dot.style.left = pct + '%';
18146        dot.title = markers[i].getAttribute('title') || '';
18147        chaptersContainer.appendChild(dot);
18148      }
18149    },
18150
18151    // ========================================================================
18152    // PLAYER BAR INFO UPDATE
18153    // ========================================================================
18154
18155    _updatePlayerBarInfo: function() {
18156      if (!this.playerBar || !this.currentEpisode) return;
18157
18158      var ep = this.currentEpisode;
18159
18160      // Title
18161      var titleEl = this.playerBar.querySelector('.mini-player__title');
18162      if (titleEl) {
18163        titleEl.textContent = ep.title;
18164        titleEl.href = ep.episodeUrl || '#';
18165      }
18166
18167      // Series
18168      var seriesEl = this.playerBar.querySelector('.mini-player__series');
18169      if (seriesEl) {
18170        seriesEl.textContent = ep.series || '';
18171        seriesEl.style.display = ep.series ? '' : 'none';
18172      }
18173
18174      // Thumbnail
18175      var thumbImg = this.playerBar.querySelector('.mini-player__thumb-img');
18176      var thumbPlaceholder = this.playerBar.querySelector('.mini-player__thumb-placeholder');
18177      if (thumbImg && thumbPlaceholder) {
18178        if (ep.thumbnail) {
18179          thumbImg.src = ep.thumbnail;
18180          thumbImg.alt = ep.title;
18181          thumbImg.style.display = '';
18182          thumbPlaceholder.style.display = 'none';
18183        } else {
18184          thumbImg.style.display = 'none';
18185          thumbPlaceholder.style.display = '';
18186        }
18187      }
18188
18189      // Speed button
18190      var speed = this.speeds[this.currentSpeedIndex];
18191      var speedBtn = this.playerBar.querySelector('.mini-player__speed-btn');
18192      if (speedBtn) {
18193        speedBtn.textContent = speed + 'x';
18194      }
18195    },
18196
18197    // ========================================================================
18198    // SYNC WITH INLINE PAGE PLAYER
18199    // ========================================================================
18200
18201    syncWithPagePlayer: function() {
18202      var pagePlayer = document.querySelector('[data-podcast-player]');
18203      if (!pagePlayer || !this.currentEpisode) return;
18204
18205      // Check if the page player is for the same episode
18206      var pageEpId = pagePlayer.getAttribute('data-episode-id');
18207      if (!pageEpId || pageEpId !== String(this.currentEpisode.episodeId)) return;
18208
18209      // Sync play state
18210      var isPlaying = this.audio && !this.audio.paused;
18211      this._updatePlayButton(isPlaying);
18212
18213      // Sync progress
18214      if (this.audio && this.audio.duration) {
18215        this.updateProgress();
18216      }
18217
18218      // Sync speed
18219      var pageSpeedBtn = pagePlayer.querySelector('[data-player-speed]');
18220      if (pageSpeedBtn) {
18221        pageSpeedBtn.textContent = this.speeds[this.currentSpeedIndex] + 'x';
18222      }
18223
18224      // Update chapter markers for mini-player
18225      this._updateChapterMarkers();
18226
18227      // Also gather additional episode info from the page player if missing
18228      if (!this.currentEpisode.thumbnail) {
18229        var thumbAttr = pagePlayer.getAttribute('data-episode-thumbnail');
18230        if (thumbAttr) {
18231          this.currentEpisode.thumbnail = thumbAttr;
18232          this._updatePlayerBarInfo();
18233        }
18234      }
18235
18236      if (!this.currentEpisode.series) {
18237        var seriesAttr = pagePlayer.getAttribute('data-episode-series');
18238        if (seriesAttr) {
18239          this.currentEpisode.series = seriesAttr;
18240          this._updatePlayerBarInfo();
18241        }
18242      }
18243
18244      if (!this.currentEpisode.episodeUrl) {
18245        var urlAttr = pagePlayer.getAttribute('data-episode-url');
18246        if (urlAttr) {
18247          this.currentEpisode.episodeUrl = urlAttr;
18248          this._updatePlayerBarInfo();
18249        }
18250      }
18251    },
18252
18253    // ========================================================================
18254    // SHOW / HIDE
18255    // ========================================================================
18256
18257    show: function() {
18258      if (!this.playerBar) return;
18259      this.playerBar.classList.add('mini-player--visible');
18260      document.body.classList.add('has-mini-player');
18261    },
18262
18263    _clearSeekOnLoad: function() {
18264      if (this._seekOnLoadHandler && this.audio) {
18265        this.audio.removeEventListener('loadedmetadata', this._seekOnLoadHandler);
18266        this._seekOnLoadHandler = null;
18267      }
18268    },
18269
18270    hide: function() {
18271      // Cancel pending debounced save
18272      if (this._saveDebounce) {
18273        clearTimeout(this._saveDebounce);
18274        this._saveDebounce = null;
18275      }
18276
18277      // Clean up pending seek handler
18278      this._clearSeekOnLoad();
18279
18280      if (this.audio) {
18281        this.audio.pause();
18282        this.audio.removeAttribute('src');
18283        this.audio.load();
18284      }
18285
18286      if (this.playerBar) {
18287        this.playerBar.classList.remove('mini-player--visible');
18288        this._mobileExpanded = false;
18289        this.playerBar.classList.remove('mini-player--expanded');
18290      }
18291
18292      document.body.classList.remove('has-mini-player');
18293      this.currentEpisode = null;
18294      this._clearSavedState();
18295    },
18296
18297    _toggleMobileExpand: function() {
18298      if (!this.playerBar) return;
18299      this._mobileExpanded = !this._mobileExpanded;
18300      this.playerBar.classList.toggle('mini-player--expanded', this._mobileExpanded);
18301
18302      var expandBtn = this.playerBar.querySelector('.mini-player__mobile-expand');
18303      if (expandBtn) {
18304        expandBtn.innerHTML = this._mobileExpanded ? SVG_CHEVRON_DOWN : SVG_CHEVRON_UP;
18305      }
18306    },
18307
18308    // ========================================================================
18309    // LOCAL STORAGE PERSISTENCE
18310    // ========================================================================
18311
18312    saveState: function() {
18313      if (!this.currentEpisode) return;
18314
18315      try {
18316        var state = {
18317          episodeId: this.currentEpisode.episodeId,
18318          slug: this.currentEpisode.slug,
18319          title: this.currentEpisode.title,
18320          audioUrl: this.currentEpisode.audioUrl,
18321          thumbnail: this.currentEpisode.thumbnail,
18322          series: this.currentEpisode.series,
18323          episodeUrl: this.currentEpisode.episodeUrl,
18324          position: this.audio ? this.audio.currentTime : 0,
18325          speedIndex: this.currentSpeedIndex,
18326          isPlaying: this.audio ? !this.audio.paused : false
18327        };
18328        localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
18329      } catch (e) {
18330        // LocalStorage not available or full
18331      }
18332    },
18333
18334    _debouncedSave: function() {
18335      var self = this;
18336      if (this._saveDebounce) return;
18337      this._saveDebounce = setTimeout(function() {
18338        self._saveDebounce = null;
18339        self.saveState();
18340      }, 2000);
18341    },
18342
18343    restoreState: function() {
18344      var saved = this._getSavedState();
18345      if (!saved || !saved.audioUrl) return;
18346      if (!isSafeAudioUrl(saved.audioUrl)) return;
18347
18348      // Restore episode data
18349      this.currentEpisode = {
18350        episodeId: saved.episodeId,
18351        slug: saved.slug || '',
18352        title: saved.title || 'Untitled Episode',
18353        audioUrl: saved.audioUrl,
18354        thumbnail: saved.thumbnail || '',
18355        series: saved.series || '',
18356        episodeUrl: saved.episodeUrl || ''
18357      };
18358
18359      // Restore speed
18360      if (typeof saved.speedIndex === 'number' && saved.speedIndex >= 0 && saved.speedIndex < this.speeds.length) {
18361        this.currentSpeedIndex = saved.speedIndex;
18362      }
18363      if (this.audio) {
18364        this.audio.playbackRate = this.speeds[this.currentSpeedIndex];
18365      }
18366
18367      // Load audio and resume if user was actively listening
18368      var shouldAutoPlay = !!saved.isPlaying;
18369
18370      if (this.audio && (!this.audio.src || this.audio.src === '' || this.audio.src !== saved.audioUrl)) {
18371        // Clean up any pending seek handler
18372        this._clearSeekOnLoad();
18373
18374        this.audio.src = saved.audioUrl;
18375        if (saved.position > 0) {
18376          // Wait for metadata to seek to position, then auto-play if needed
18377          var self = this;
18378          var seekOnLoad = function() {
18379            if (saved.position < self.audio.duration) {
18380              self.audio.currentTime = saved.position;
18381            }
18382            self.updateProgress();
18383            if (shouldAutoPlay) {
18384              safePlay(self.audio);
18385            }
18386            self.audio.removeEventListener('loadedmetadata', seekOnLoad);
18387            self._seekOnLoadHandler = null;
18388          };
18389          this._seekOnLoadHandler = seekOnLoad;
18390          this.audio.addEventListener('loadedmetadata', seekOnLoad);
18391        } else if (shouldAutoPlay) {
18392          safePlay(this.audio);
18393        }
18394      }
18395
18396      // Show the player bar with restored info
18397      this._updatePlayerBarInfo();
18398      this.show();
18399    },
18400
18401    _getSavedState: function() {
18402      try {
18403        var data = localStorage.getItem(STORAGE_KEY);
18404        if (data) return JSON.parse(data);
18405      } catch (e) {
18406        // Ignore parse errors
18407      }
18408      return null;
18409    },
18410
18411    _clearSavedState: function() {
18412      try {
18413        localStorage.removeItem(STORAGE_KEY);
18414      } catch (e) {
18415        // Ignore
18416      }
18417    },
18418
18419    // ========================================================================
18420    // PAGE ELEMENT BINDING
18421    // ========================================================================
18422
18423    bindPageElements: function() {
18424      var self = this;
18425
18426      // Bind play buttons with [data-audio-url]
18427      var playButtons = document.querySelectorAll('[data-audio-url]');
18428      for (var i = 0; i < playButtons.length; i++) {
18429        // Avoid re-binding
18430        if (playButtons[i]._racemobBound) continue;
18431        playButtons[i]._racemobBound = true;
18432
18433        (function(btn) {
18434          btn.addEventListener('click', function(e) {
18435            e.preventDefault();
18436            e.stopPropagation();
18437            var episodeData = {
18438              episodeId: btn.getAttribute('data-episode-id'),
18439              slug: btn.getAttribute('data-slug') || '',
18440              title: btn.getAttribute('data-title') || '',
18441              audioUrl: btn.getAttribute('data-audio-url'),
18442              thumbnail: btn.getAttribute('data-thumbnail') || '',
18443              series: btn.getAttribute('data-series') || '',
18444              episodeUrl: btn.getAttribute('data-episode-url') || ''
18445            };
18446
18447            // If same episode is already playing, toggle pause
18448            if (self.currentEpisode &&
18449                self.currentEpisode.episodeId === episodeData.episodeId &&
18450                self.audio && !self.audio.paused) {
18451              self.pause();
18452            } else {
18453              self.play(episodeData);
18454            }
18455          });
18456        })(playButtons[i]);
18457      }
18458
18459      // Bind seek buttons with [data-seek-time]
18460      var seekButtons = document.querySelectorAll('[data-seek-time]');
18461      for (var j = 0; j < seekButtons.length; j++) {
18462        if (seekButtons[j]._racemobBound) continue;
18463        seekButtons[j]._racemobBound = true;
18464
18465        (function(btn) {
18466          btn.addEventListener('click', function(e) {
18467            e.preventDefault();
18468            var time = btn.getAttribute('data-seek-time');
18469            // If no episode loaded, try to load from page player data
18470            if (!self.currentEpisode) {
18471              var pagePlayer = document.querySelector('[data-podcast-player]');
18472              if (pagePlayer) {
18473                self.play({
18474                  episodeId: pagePlayer.getAttribute('data-episode-id'),
18475                  slug: pagePlayer.getAttribute('data-episode-slug') || '',
18476                  title: pagePlayer.getAttribute('data-episode-title') || '',
18477                  audioUrl: pagePlayer.getAttribute('data-audio-src'),
18478                  thumbnail: pagePlayer.getAttribute('data-episode-thumbnail') || '',
18479                  series: pagePlayer.getAttribute('data-episode-series') || '',
18480                  episodeUrl: pagePlayer.getAttribute('data-episode-url') || ''
18481                });
18482              }
18483            }
18484            self.seek(time);
18485          });
18486        })(seekButtons[j]);
18487      }
18488
18489      // Bind inline player play/pause button
18490      var pagePlayBtn = document.querySelector('[data-player-play]');
18491      if (pagePlayBtn && !pagePlayBtn._racemobBound) {
18492        pagePlayBtn._racemobBound = true;
18493        pagePlayBtn.addEventListener('click', function(e) {
18494          e.preventDefault();
18495          var pagePlayer = document.querySelector('[data-podcast-player]');
18496          if (!pagePlayer) { self.toggle(); return; }
18497
18498          var pageEpId = pagePlayer.getAttribute('data-episode-id');
18499          var isSameEpisode = self.currentEpisode &&
18500            String(self.currentEpisode.episodeId) === String(pageEpId);
18501
18502          if (isSameEpisode) {
18503            // Same episode: toggle play/pause
18504            self.toggle();
18505          } else {
18506            // Different episode (or none loaded): load the page's episode
18507            self.play({
18508              episodeId: pageEpId,
18509              slug: pagePlayer.getAttribute('data-episode-slug') || '',
18510              title: pagePlayer.getAttribute('data-episode-title') || '',
18511              audioUrl: pagePlayer.getAttribute('data-audio-src'),
18512              thumbnail: pagePlayer.getAttribute('data-episode-thumbnail') || '',
18513              series: pagePlayer.getAttribute('data-episode-series') || '',
18514              episodeUrl: pagePlayer.getAttribute('data-episode-url') || ''
18515            });
18516          }
18517        });
18518      }
18519
18520      // Bind inline player progress bar click-to-seek
18521      var pageBar = document.querySelector('[data-player-bar]');
18522      if (pageBar && !pageBar._racemobBound) {
18523        pageBar._racemobBound = true;
18524        pageBar.addEventListener('click', function(e) {
18525          if (!self.audio || !self.audio.duration) return;
18526          var rect = pageBar.getBoundingClientRect();
18527          var pct = (e.clientX - rect.left) / rect.width;
18528          pct = Math.max(0, Math.min(1, pct));
18529          self.audio.currentTime = pct * self.audio.duration;
18530          self.updateProgress();
18531        });
18532      }
18533
18534      // Bind inline player speed button
18535      var pageSpeedBtn = document.querySelector('[data-player-speed]');
18536      if (pageSpeedBtn && !pageSpeedBtn._racemobBound) {
18537        pageSpeedBtn._racemobBound = true;
18538        pageSpeedBtn.addEventListener('click', function(e) {
18539          e.preventDefault();
18540          self.cycleSpeed();
18541        });
18542      }
18543
18544      // Bind skip offset buttons (e.g. -15s, +30s)
18545      var skipBtns = document.querySelectorAll('[data-seek-offset]');
18546      for (var k = 0; k < skipBtns.length; k++) {
18547        if (skipBtns[k]._racemobBound) continue;
18548        skipBtns[k]._racemobBound = true;
18549
18550        (function(btn) {
18551          btn.addEventListener('click', function(e) {
18552            e.preventDefault();
18553            var offset = parseInt(btn.getAttribute('data-seek-offset'), 10);
18554            if (isNaN(offset)) return;
18555
18556            // If no episode loaded, load from page player first
18557            if (!self.currentEpisode || !self.audio.src) {
18558              var pagePlayer = document.querySelector('[data-podcast-player]');
18559              if (pagePlayer) {
18560                self.play({
18561                  episodeId: pagePlayer.getAttribute('data-episode-id'),
18562                  slug: pagePlayer.getAttribute('data-episode-slug') || '',
18563                  title: pagePlayer.getAttribute('data-episode-title') || '',
18564                  audioUrl: pagePlayer.getAttribute('data-audio-src'),
18565                  thumbnail: pagePlayer.getAttribute('data-episode-thumbnail') || '',
18566                  series: pagePlayer.getAttribute('data-episode-series') || '',
18567                  episodeUrl: pagePlayer.getAttribute('data-episode-url') || ''
18568                });
18569              }
18570              return;
18571            }
18572
18573            var newTime = self.audio.currentTime + offset;
18574            newTime = Math.max(0, Math.min(newTime, self.audio.duration || Infinity));
18575            self.audio.currentTime = newTime;
18576            self.updateProgress();
18577          });
18578        })(skipBtns[k]);
18579      }
18580    },
18581
18582    // ========================================================================
18583    // UTILITIES
18584    // ========================================================================
18585
18586    formatTime: function(seconds) {
18587      if (!seconds || isNaN(seconds)) return '0:00';
18588      seconds = Math.floor(seconds);
18589      var h = Math.floor(seconds / 3600);
18590      var m = Math.floor((seconds % 3600) / 60);
18591      var s = seconds % 60;
18592
18593      if (h > 0) {
18594        return h + ':' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
18595      }
18596      return m + ':' + (s < 10 ? '0' : '') + s;
18597    },
18598
18599    _ensureFontAwesome: function() {
18600      // Check if Font Awesome is already loaded
18601      var links = document.querySelectorAll('link[rel="stylesheet"]');
18602      for (var i = 0; i < links.length; i++) {
18603        if (links[i].href && links[i].href.indexOf('font-awesome') !== -1) return;
18604      }
18605      // Inject it
18606      var link = document.createElement('link');
18607      link.rel = 'stylesheet';
18608      link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css';
18609      link.integrity = 'sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==';
18610      link.crossOrigin = 'anonymous';
18611      link.referrerPolicy = 'no-referrer';
18612      document.head.appendChild(link);
18613    }
18614  };
18615
18616  // Expose globally
18617  window.RaceMobPlayer = RaceMobPlayer;
18618
18619  // ========================================================================
18620  // EVENT LISTENERS — Turbolinks compatible
18621  // ========================================================================
18622
18623  function onPageReady() {
18624    window.RaceMobPlayer.init();
18625  }
18626
18627  var _turbolinksLoaded = false;
18628
18629  // Turbolinks classic (page:load) and Turbolinks 5 (turbolinks:load)
18630  // turbolinks:load fires on initial page load AND subsequent navigations
18631  document.addEventListener('page:load', function() { _turbolinksLoaded = true; onPageReady(); });
18632  document.addEventListener('turbolinks:load', function() { _turbolinksLoaded = true; onPageReady(); });
18633
18634  // Fallback for pages without Turbolinks
18635  document.addEventListener('DOMContentLoaded', function() {
18636    if (!_turbolinksLoaded) onPageReady();
18637  });
18638
18639  // Save state immediately before page unload so the next page can resume
18640  window.addEventListener('beforeunload', function() {
18641    if (window.RaceMobPlayer) {
18642      window.RaceMobPlayer.saveState();
18643    }
18644  });
18645})();
18646/**
18647 * Base JavaScript Bundle
18648 *
18649 * UNIVERSAL DEPENDENCIES - Loaded on every page across the entire site
18650 *
18651 * This file contains only the absolute essentials that every page needs:
18652 * - Core framework libraries (jQuery, Rails UJS)
18653 * - Site-wide utilities (modals, notifications, password toggles)
18654 * - Global helper functions
18655 *
18656 * DO NOT add page-specific code here. If it's not needed on EVERY page,
18657 * it belongs in a page-specific bundle (application.js, homepage_bundle.js, etc.)
18658 */
18659
18660// ============================================================================
18661// CORE FRAMEWORK DEPENDENCIES
18662// ============================================================================
18663
18664
18665
18666
18667// ============================================================================
18668// SITE-WIDE UTILITIES
18669// ============================================================================
18670
18671// Modal utilities - safe overlay close, focus management, accessibility
18672
18673
18674// Blessed modal chassis (rm-modal) - triggers, AJAX partial load, focus trap
18675
18676
18677
18678// Chassis follow bar (rm-followbar) — the ONE shared IntersectionObserver that toggles
18679// .is-visible when a page's hero scrolls out, plus the tab-rail scroll-into-view. Lands
18680// with the race rebuild, which is the first page to render shared/_rm_followbar; the
18681// search rebuild carries its own copy of the observer inside search_rebuild.js.
18682
18683
18684
18685// Shared Follow / Following button ([data-follow-toggle]); the Going tab and the athlete profile hero.
18686
18687// Community widget (comment / high-five / report / requests inbox); reports through the toast.
18688
18689
18690// Shared race save / unsave (bookmark) toggle; search results and the athlete profile's Races for you.
18691
18692
18693// UI helpers - notification system used across the site
18694
18695
18696// Error reporter - wraps window.Sentry (loaded via shared/_sentry.html.erb)
18697// so .catch handlers can forward errors with structured context.
18698
18699
18700// ============================================================================
18701// UNIVERSAL COMPONENTS (used on nearly every page)
18702// ============================================================================
18703
18704// Authentication utilities - shared auth logic (MUST load before auth_modal)
18705
18706
18707// Declarative submit handler for forms rendered by the
18708// shared/_auth_form_fields partial. Idempotent + no-op when no
18709// [data-auth-form] forms are present. Depends on auth_utils.
18710
18711
18712// Authentication modal - login/signup (used on 5/6 bundles)
18713
18714
18715// Compat shim: defines window.showDialogLink for AUTH urls on layouts whose
18716// page bundle no longer includes functions.js. Idempotent — no-op if
18717// functions.js is already loaded. See app/assets/javascripts/auth_modal_compat.js
18718// for the regression history.
18719
18720
18721// Navigation - header navigation bar (used on 5/6 bundles)
18722// (navigation.js retained for the email verification banner + action gates; its
18723//  old nav-element handlers no-op now that shared/_navigation.html.erb renders rm-sitenav)
18724
18725
18726// rm-sitenav v2 (blessed 2026-07-10) - avatar account menu + mobile drawer toggles
18727
18728
18729// ============================================================================
18730// SITE-WIDE WIDGETS
18731// ============================================================================
18732
18733// Podcast mini-player — persistent bottom bar that restores from localStorage
18734// on every page load, regardless of layout. Includes Turbolinks persistence
18735// handler for pages that use Turbolinks navigation.
18736
18737
18738
18739// ============================================================================
18740// GLOBAL FUNCTIONS
18741// ============================================================================
18742
18743// Password visibility toggle is loaded as part of auth_modal.js
18744// (defined at the end of that file as window.togglePassword)
18745// This is used by auth modal, onboarding, and profile forms
18746
18747// ============================================================================
18748// CONFIGURATION
18749// ============================================================================
18750
18751// Disable automatic discovery of clickjacking vulnerability
18752// This prevents jQuery from automatically making all links open in new tabs
18753if (typeof jQuery !== 'undefined') {
18754  jQuery.ajaxSetup({
18755    headers: {
18756      'X-CSRF-Token': jQuery('meta[name="csrf-token"]').attr('content')
18757    }
18758  });
18759}
18760;
18761( function( factory ) {
18762	if ( typeof define === "function" && define.amd ) {
18763
18764		// AMD. Register as an anonymous module.
18765		define( [ "jquery" ], factory );
18766	} else {
18767
18768		// Browser globals
18769		factory( jQuery );
18770	}
18771} ( function( $ ) {
18772
18773$.ui = $.ui || {};
18774
18775return $.ui.version = "1.12.1";
18776
18777} ) );
18778
18779
18780/*!
18781 * jQuery UI :data 1.12.1
18782 * http://jqueryui.com
18783 *
18784 * Copyright jQuery Foundation and other contributors
18785 * Released under the MIT license.
18786 * http://jquery.org/license
18787 */
18788
18789//>>label: :data Selector
18790//>>group: Core
18791//>>description: Selects elements which have data stored under the specified key.
18792//>>docs: http://api.jqueryui.com/data-selector/
18793
18794( function( factory ) {
18795	if ( typeof define === "function" && define.amd ) {
18796
18797		// AMD. Register as an anonymous module.
18798		define( [ "jquery", "./version" ], factory );
18799	} else {
18800
18801		// Browser globals
18802		factory( jQuery );
18803	}
18804} ( function( $ ) {
18805return $.extend( $.expr[ ":" ], {
18806	data: $.expr.createPseudo ?
18807		$.expr.createPseudo( function( dataName ) {
18808			return function( elem ) {
18809				return !!$.data( elem, dataName );
18810			};
18811		} ) :
18812
18813		// Support: jQuery <1.8
18814		function( elem, i, match ) {
18815			return !!$.data( elem, match[ 3 ] );
18816		}
18817} );
18818} ) );
18819
18820
18821/*!
18822 * jQuery UI Disable Selection 1.12.1
18823 * http://jqueryui.com
18824 *
18825 * Copyright jQuery Foundation and other contributors
18826 * Released under the MIT license.
18827 * http://jquery.org/license
18828 */
18829
18830//>>label: disableSelection
18831//>>group: Core
18832//>>description: Disable selection of text content within the set of matched elements.
18833//>>docs: http://api.jqueryui.com/disableSelection/
18834
18835// This file is deprecated
18836( function( factory ) {
18837	if ( typeof define === "function" && define.amd ) {
18838
18839		// AMD. Register as an anonymous module.
18840		define( [ "jquery", "./version" ], factory );
18841	} else {
18842
18843		// Browser globals
18844		factory( jQuery );
18845	}
18846} ( function( $ ) {
18847
18848return $.fn.extend( {
18849	disableSelection: ( function() {
18850		var eventType = "onselectstart" in document.createElement( "div" ) ?
18851			"selectstart" :
18852			"mousedown";
18853
18854		return function() {
18855			return this.on( eventType + ".ui-disableSelection", function( event ) {
18856				event.preventDefault();
18857			} );
18858		};
18859	} )(),
18860
18861	enableSelection: function() {
18862		return this.off( ".ui-disableSelection" );
18863	}
18864} );
18865
18866} ) );
18867
18868
18869/*!
18870 * jQuery UI Focusable 1.12.1
18871 * http://jqueryui.com
18872 *
18873 * Copyright jQuery Foundation and other contributors
18874 * Released under the MIT license.
18875 * http://jquery.org/license
18876 */
18877
18878//>>label: :focusable Selector
18879//>>group: Core
18880//>>description: Selects elements which can be focused.
18881//>>docs: http://api.jqueryui.com/focusable-selector/
18882
18883( function( factory ) {
18884	if ( typeof define === "function" && define.amd ) {
18885
18886		// AMD. Register as an anonymous module.
18887		define( [ "jquery", "./version" ], factory );
18888	} else {
18889
18890		// Browser globals
18891		factory( jQuery );
18892	}
18893} ( function( $ ) {
18894
18895// Selectors
18896$.ui.focusable = function( element, hasTabindex ) {
18897	var map, mapName, img, focusableIfVisible, fieldset,
18898		nodeName = element.nodeName.toLowerCase();
18899
18900	if ( "area" === nodeName ) {
18901		map = element.parentNode;
18902		mapName = map.name;
18903		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
18904			return false;
18905		}
18906		img = $( "img[usemap='#" + mapName + "']" );
18907		return img.length > 0 && img.is( ":visible" );
18908	}
18909
18910	if ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) {
18911		focusableIfVisible = !element.disabled;
18912
18913		if ( focusableIfVisible ) {
18914
18915			// Form controls within a disabled fieldset are disabled.
18916			// However, controls within the fieldset's legend do not get disabled.
18917			// Since controls generally aren't placed inside legends, we skip
18918			// this portion of the check.
18919			fieldset = $( element ).closest( "fieldset" )[ 0 ];
18920			if ( fieldset ) {
18921				focusableIfVisible = !fieldset.disabled;
18922			}
18923		}
18924	} else if ( "a" === nodeName ) {
18925		focusableIfVisible = element.href || hasTabindex;
18926	} else {
18927		focusableIfVisible = hasTabindex;
18928	}
18929
18930	return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) );
18931};
18932
18933// Support: IE 8 only
18934// IE 8 doesn't resolve inherit to visible/hidden for computed values
18935function visible( element ) {
18936	var visibility = element.css( "visibility" );
18937	while ( visibility === "inherit" ) {
18938		element = element.parent();
18939		visibility = element.css( "visibility" );
18940	}
18941	return visibility !== "hidden";
18942}
18943
18944$.extend( $.expr[ ":" ], {
18945	focusable: function( element ) {
18946		return $.ui.focusable( element, $.attr( element, "tabindex" ) != null );
18947	}
18948} );
18949
18950return $.ui.focusable;
18951
18952} ) );
18953
18954( function( factory ) {
18955	if ( typeof define === "function" && define.amd ) {
18956
18957		// AMD. Register as an anonymous module.
18958		define( [ "jquery", "./version" ], factory );
18959	} else {
18960
18961		// Browser globals
18962		factory( jQuery );
18963	}
18964} ( function( $ ) {
18965
18966// Support: IE8 Only
18967// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop
18968// with a string, so we need to find the proper form.
18969return $.fn.form = function() {
18970	return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form );
18971};
18972
18973} ) );
18974
18975( function( factory ) {
18976	if ( typeof define === "function" && define.amd ) {
18977
18978		// AMD. Register as an anonymous module.
18979		define( [ "jquery", "./version" ], factory );
18980	} else {
18981
18982		// Browser globals
18983		factory( jQuery );
18984	}
18985} ( function( $ ) {
18986
18987// This file is deprecated
18988return $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
18989} ) );
18990
18991
18992/*!
18993 * jQuery UI Keycode 1.12.1
18994 * http://jqueryui.com
18995 *
18996 * Copyright jQuery Foundation and other contributors
18997 * Released under the MIT license.
18998 * http://jquery.org/license
18999 */
19000
19001//>>label: Keycode
19002//>>group: Core
19003//>>description: Provide keycodes as keynames
19004//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/
19005
19006( function( factory ) {
19007	if ( typeof define === "function" && define.amd ) {
19008
19009		// AMD. Register as an anonymous module.
19010		define( [ "jquery", "./version" ], factory );
19011	} else {
19012
19013		// Browser globals
19014		factory( jQuery );
19015	}
19016} ( function( $ ) {
19017return $.ui.keyCode = {
19018	BACKSPACE: 8,
19019	COMMA: 188,
19020	DELETE: 46,
19021	DOWN: 40,
19022	END: 35,
19023	ENTER: 13,
19024	ESCAPE: 27,
19025	HOME: 36,
19026	LEFT: 37,
19027	PAGE_DOWN: 34,
19028	PAGE_UP: 33,
19029	PERIOD: 190,
19030	RIGHT: 39,
19031	SPACE: 32,
19032	TAB: 9,
19033	UP: 38
19034};
19035
19036} ) );
19037
19038( function( factory ) {
19039	if ( typeof define === "function" && define.amd ) {
19040
19041		// AMD. Register as an anonymous module.
19042		define( [ "jquery", "./version" ], factory );
19043	} else {
19044
19045		// Browser globals
19046		factory( jQuery );
19047	}
19048} ( function( $ ) {
19049
19050// Internal use only
19051return $.ui.escapeSelector = ( function() {
19052	var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g;
19053	return function( selector ) {
19054		return selector.replace( selectorEscape, "\\$1" );
19055	};
19056} )();
19057
19058} ) );
19059
19060
19061
19062/*!
19063 * jQuery UI Labels 1.12.1
19064 * http://jqueryui.com
19065 *
19066 * Copyright jQuery Foundation and other contributors
19067 * Released under the MIT license.
19068 * http://jquery.org/license
19069 */
19070
19071//>>label: labels
19072//>>group: Core
19073//>>description: Find all the labels associated with a given input
19074//>>docs: http://api.jqueryui.com/labels/
19075
19076( function( factory ) {
19077	if ( typeof define === "function" && define.amd ) {
19078
19079		// AMD. Register as an anonymous module.
19080		define( [ "jquery", "./version", "./escape-selector" ], factory );
19081	} else {
19082
19083		// Browser globals
19084		factory( jQuery );
19085	}
19086} ( function( $ ) {
19087
19088return $.fn.labels = function() {
19089	var ancestor, selector, id, labels, ancestors;
19090
19091	// Check control.labels first
19092	if ( this[ 0 ].labels && this[ 0 ].labels.length ) {
19093		return this.pushStack( this[ 0 ].labels );
19094	}
19095
19096	// Support: IE <= 11, FF <= 37, Android <= 2.3 only
19097	// Above browsers do not support control.labels. Everything below is to support them
19098	// as well as document fragments. control.labels does not work on document fragments
19099	labels = this.eq( 0 ).parents( "label" );
19100
19101	// Look for the label based on the id
19102	id = this.attr( "id" );
19103	if ( id ) {
19104
19105		// We don't search against the document in case the element
19106		// is disconnected from the DOM
vendor: 5,459 bytes, lines 19107-19324
19107		ancestor = this.eq( 0 ).parents().last();
19108
19109		// Get a full set of top level ancestors
19110		ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() );
19111
19112		// Create a selector for the label based on the id
19113		selector = "label[for='" + $.ui.escapeSelector( id ) + "']";
19114
19115		labels = labels.add( ancestors.find( selector ).addBack( selector ) );
19116
19117	}
19118
19119	// Return whatever we have found for labels
19120	return this.pushStack( labels );
19121};
19122
19123} ) );
19124
19125
19126/*!
19127 * jQuery UI Support for jQuery core 1.7.x 1.12.1
19128 * http://jqueryui.com
19129 *
19130 * Copyright jQuery Foundation and other contributors
19131 * Released under the MIT license.
19132 * http://jquery.org/license
19133 *
19134 */
19135
19136//>>label: jQuery 1.7 Support
19137//>>group: Core
19138//>>description: Support version 1.7.x of jQuery core
19139
19140( function( factory ) {
19141	if ( typeof define === "function" && define.amd ) {
19142
19143		// AMD. Register as an anonymous module.
19144		define( [ "jquery", "./version" ], factory );
19145	} else {
19146
19147		// Browser globals
19148		factory( jQuery );
19149	}
19150}( function( $ ) {
19151
19152// Support: jQuery 1.7 only
19153// Not a great way to check versions, but since we only support 1.7+ and only
19154// need to detect <1.8, this is a simple check that should suffice. Checking
19155// for "1.7." would be a bit safer, but the version string is 1.7, not 1.7.0
19156// and we'll never reach 1.70.0 (if we do, we certainly won't be supporting
19157// 1.7 anymore). See #11197 for why we're not using feature detection.
19158if ( $.fn.jquery.substring( 0, 3 ) === "1.7" ) {
19159
19160	// Setters for .innerWidth(), .innerHeight(), .outerWidth(), .outerHeight()
19161	// Unlike jQuery Core 1.8+, these only support numeric values to set the
19162	// dimensions in pixels
19163	$.each( [ "Width", "Height" ], function( i, name ) {
19164		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
19165			type = name.toLowerCase(),
19166			orig = {
19167				innerWidth: $.fn.innerWidth,
19168				innerHeight: $.fn.innerHeight,
19169				outerWidth: $.fn.outerWidth,
19170				outerHeight: $.fn.outerHeight
19171			};
19172
19173		function reduce( elem, size, border, margin ) {
19174			$.each( side, function() {
19175				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
19176				if ( border ) {
19177					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
19178				}
19179				if ( margin ) {
19180					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
19181				}
19182			} );
19183			return size;
19184		}
19185
19186		$.fn[ "inner" + name ] = function( size ) {
19187			if ( size === undefined ) {
19188				return orig[ "inner" + name ].call( this );
19189			}
19190
19191			return this.each( function() {
19192				$( this ).css( type, reduce( this, size ) + "px" );
19193			} );
19194		};
19195
19196		$.fn[ "outer" + name ] = function( size, margin ) {
19197			if ( typeof size !== "number" ) {
19198				return orig[ "outer" + name ].call( this, size );
19199			}
19200
19201			return this.each( function() {
19202				$( this ).css( type, reduce( this, size, true, margin ) + "px" );
19203			} );
19204		};
19205	} );
19206
19207	$.fn.addBack = function( selector ) {
19208		return this.add( selector == null ?
19209			this.prevObject : this.prevObject.filter( selector )
19210		);
19211	};
19212}
19213
19214} ) );
19215
19216( function( factory ) {
19217	if ( typeof define === "function" && define.amd ) {
19218
19219		// AMD. Register as an anonymous module.
19220		define( [ "jquery", "./version" ], factory );
19221	} else {
19222
19223		// Browser globals
19224		factory( jQuery );
19225	}
19226} ( function( $ ) {
19227
19228// $.ui.plugin is deprecated. Use $.widget() extensions instead.
19229return $.ui.plugin = {
19230	add: function( module, option, set ) {
19231		var i,
19232			proto = $.ui[ module ].prototype;
19233		for ( i in set ) {
19234			proto.plugins[ i ] = proto.plugins[ i ] || [];
19235			proto.plugins[ i ].push( [ option, set[ i ] ] );
19236		}
19237	},
19238	call: function( instance, name, args, allowDisconnected ) {
19239		var i,
19240			set = instance.plugins[ name ];
19241
19242		if ( !set ) {
19243			return;
19244		}
19245
19246		if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode ||
19247				instance.element[ 0 ].parentNode.nodeType === 11 ) ) {
19248			return;
19249		}
19250
19251		for ( i = 0; i < set.length; i++ ) {
19252			if ( instance.options[ set[ i ][ 0 ] ] ) {
19253				set[ i ][ 1 ].apply( instance.element, args );
19254			}
19255		}
19256	}
19257};
19258
19259} ) );
19260
19261( function( factory ) {
19262	if ( typeof define === "function" && define.amd ) {
19263
19264		// AMD. Register as an anonymous module.
19265		define( [ "jquery", "./version" ], factory );
19266	} else {
19267
19268		// Browser globals
19269		factory( jQuery );
19270	}
19271} ( function( $ ) {
19272return $.ui.safeActiveElement = function( document ) {
19273	var activeElement;
19274
19275	// Support: IE 9 only
19276	// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
19277	try {
19278		activeElement = document.activeElement;
19279	} catch ( error ) {
19280		activeElement = document.body;
19281	}
19282
19283	// Support: IE 9 - 11 only
19284	// IE may return null instead of an element
19285	// Interestingly, this only seems to occur when NOT in an iframe
19286	if ( !activeElement ) {
19287		activeElement = document.body;
19288	}
19289
19290	// Support: IE 11 only
19291	// IE11 returns a seemingly empty object in some cases when accessing
19292	// document.activeElement from an <iframe>
19293	if ( !activeElement.nodeName ) {
19294		activeElement = document.body;
19295	}
19296
19297	return activeElement;
19298};
19299
19300} ) );
19301
19302( function( factory ) {
19303	if ( typeof define === "function" && define.amd ) {
19304
19305		// AMD. Register as an anonymous module.
19306		define( [ "jquery", "./version" ], factory );
19307	} else {
19308
19309		// Browser globals
19310		factory( jQuery );
19311	}
19312} ( function( $ ) {
19313return $.ui.safeBlur = function( element ) {
19314
19315	// Support: IE9 - 10 only
19316	// If the <body> is blurred, IE will switch windows, see #9420
19317	if ( element && element.nodeName.toLowerCase() !== "body" ) {
19318		$( element ).trigger( "blur" );
19319	}
19320};
19321
19322} ) );
19323
19324
19325/*!
19326 * jQuery UI Scroll Parent 1.12.1
19327 * http://jqueryui.com
19328 *
19329 * Copyright jQuery Foundation and other contributors
19330 * Released under the MIT license.
19331 * http://jquery.org/license
19332 */
19333
19334//>>label: scrollParent
19335//>>group: Core
19336//>>description: Get the closest ancestor element that is scrollable.
19337//>>docs: http://api.jqueryui.com/scrollParent/
19338
19339( function( factory ) {
19340	if ( typeof define === "function" && define.amd ) {
19341
19342		// AMD. Register as an anonymous module.
19343		define( [ "jquery", "./version" ], factory );
19344	} else {
19345
19346		// Browser globals
19347		factory( jQuery );
19348	}
19349} ( function( $ ) {
19350
19351return $.fn.scrollParent = function( includeHidden ) {
19352	var position = this.css( "position" ),
19353		excludeStaticParent = position === "absolute",
19354		overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
19355		scrollParent = this.parents().filter( function() {
19356			var parent = $( this );
19357			if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
19358				return false;
19359			}
19360			return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) +
19361				parent.css( "overflow-x" ) );
19362		} ).eq( 0 );
19363
19364	return position === "fixed" || !scrollParent.length ?
19365		$( this[ 0 ].ownerDocument || document ) :
19366		scrollParent;
19367};
19368
19369} ) );
19370
19371
19372
19373/*!
19374 * jQuery UI Tabbable 1.12.1
19375 * http://jqueryui.com
19376 *
19377 * Copyright jQuery Foundation and other contributors
19378 * Released under the MIT license.
19379 * http://jquery.org/license
19380 */
19381
19382//>>label: :tabbable Selector
19383//>>group: Core
19384//>>description: Selects elements which can be tabbed to.
19385//>>docs: http://api.jqueryui.com/tabbable-selector/
19386
19387( function( factory ) {
19388	if ( typeof define === "function" && define.amd ) {
19389
19390		// AMD. Register as an anonymous module.
19391		define( [ "jquery", "./version", "./focusable" ], factory );
19392	} else {
19393
19394		// Browser globals
19395		factory( jQuery );
19396	}
19397} ( function( $ ) {
19398
19399return $.extend( $.expr[ ":" ], {
19400	tabbable: function( element ) {
19401		var tabIndex = $.attr( element, "tabindex" ),
19402			hasTabindex = tabIndex != null;
19403		return ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex );
19404	}
19405} );
19406
19407} ) );
19408
19409
19410/*!
19411 * jQuery UI Unique ID 1.12.1
19412 * http://jqueryui.com
19413 *
19414 * Copyright jQuery Foundation and other contributors
19415 * Released under the MIT license.
19416 * http://jquery.org/license
19417 */
19418
19419//>>label: uniqueId
19420//>>group: Core
19421//>>description: Functions to generate and remove uniqueId's
19422//>>docs: http://api.jqueryui.com/uniqueId/
19423
19424( function( factory ) {
19425	if ( typeof define === "function" && define.amd ) {
19426
19427		// AMD. Register as an anonymous module.
19428		define( [ "jquery", "./version" ], factory );
19429	} else {
19430
19431		// Browser globals
19432		factory( jQuery );
19433	}
19434} ( function( $ ) {
19435
19436return $.fn.extend( {
19437	uniqueId: ( function() {
19438		var uuid = 0;
19439
19440		return function() {
19441			return this.each( function() {
19442				if ( !this.id ) {
19443					this.id = "ui-id-" + ( ++uuid );
19444				}
19445			} );
19446		};
19447	} )(),
19448
19449	removeUniqueId: function() {
19450		return this.each( function() {
19451			if ( /^ui-id-\d+$/.test( this.id ) ) {
19452				$( this ).removeAttr( "id" );
19453			}
19454		} );
19455	}
19456} );
19457
19458} ) );
19459
19460
19461
19462
19463
19464
19465
19466
19467
19468
19469
19470
19471
19472
19473
19474
19475
19476/*!
19477 * jQuery UI Effects 1.12.1
19478 * http://jqueryui.com
19479 *
19480 * Copyright jQuery Foundation and other contributors
19481 * Released under the MIT license.
19482 * http://jquery.org/license
19483 */
19484
19485//>>label: Effects Core
19486//>>group: Effects
19487// jscs:disable maximumLineLength
19488//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects.
19489// jscs:enable maximumLineLength
19490//>>docs: http://api.jqueryui.com/category/effects-core/
19491//>>demos: http://jqueryui.com/effect/
19492
19493( function( factory ) {
19494	if ( typeof define === "function" && define.amd ) {
19495
19496		// AMD. Register as an anonymous module.
19497		define( [ "jquery", "./version" ], factory );
19498	} else {
19499
19500		// Browser globals
19501		factory( jQuery );
19502	}
19503}( function( $ ) {
vendor: 40,020 bytes, lines 19503-21107
19503
19504
19505var dataSpace = "ui-effects-",
19506	dataSpaceStyle = "ui-effects-style",
19507	dataSpaceAnimated = "ui-effects-animated",
19508
19509	// Create a local jQuery because jQuery Color relies on it and the
19510	// global may not exist with AMD and a custom build (#10199)
19511	jQuery = $;
19512
19513$.effects = {
19514	effect: {}
19515};
19516
19517/*!
19518 * jQuery Color Animations v2.1.2
19519 * https://github.com/jquery/jquery-color
19520 *
19521 * Copyright 2014 jQuery Foundation and other contributors
19522 * Released under the MIT license.
19523 * http://jquery.org/license
19524 *
19525 * Date: Wed Jan 16 08:47:09 2013 -0600
19526 */
19527( function( jQuery, undefined ) {
19528
19529	var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " +
19530		"borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",
19531
19532	// Plusequals test for += 100 -= 100
19533	rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
19534
19535	// A set of RE's that can match strings and generate color tuples.
19536	stringParsers = [ {
19537			re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
19538			parse: function( execResult ) {
19539				return [
19540					execResult[ 1 ],
19541					execResult[ 2 ],
19542					execResult[ 3 ],
19543					execResult[ 4 ]
19544				];
19545			}
19546		}, {
19547			re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
19548			parse: function( execResult ) {
19549				return [
19550					execResult[ 1 ] * 2.55,
19551					execResult[ 2 ] * 2.55,
19552					execResult[ 3 ] * 2.55,
19553					execResult[ 4 ]
19554				];
19555			}
19556		}, {
19557
19558			// This regex ignores A-F because it's compared against an already lowercased string
19559			re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
19560			parse: function( execResult ) {
19561				return [
19562					parseInt( execResult[ 1 ], 16 ),
19563					parseInt( execResult[ 2 ], 16 ),
19564					parseInt( execResult[ 3 ], 16 )
19565				];
19566			}
19567		}, {
19568
19569			// This regex ignores A-F because it's compared against an already lowercased string
19570			re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
19571			parse: function( execResult ) {
19572				return [
19573					parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
19574					parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
19575					parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
19576				];
19577			}
19578		}, {
19579			re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
19580			space: "hsla",
19581			parse: function( execResult ) {
19582				return [
19583					execResult[ 1 ],
19584					execResult[ 2 ] / 100,
19585					execResult[ 3 ] / 100,
19586					execResult[ 4 ]
19587				];
19588			}
19589		} ],
19590
19591	// JQuery.Color( )
19592	color = jQuery.Color = function( color, green, blue, alpha ) {
19593		return new jQuery.Color.fn.parse( color, green, blue, alpha );
19594	},
19595	spaces = {
19596		rgba: {
19597			props: {
19598				red: {
19599					idx: 0,
19600					type: "byte"
19601				},
19602				green: {
19603					idx: 1,
19604					type: "byte"
19605				},
19606				blue: {
19607					idx: 2,
19608					type: "byte"
19609				}
19610			}
19611		},
19612
19613		hsla: {
19614			props: {
19615				hue: {
19616					idx: 0,
19617					type: "degrees"
19618				},
19619				saturation: {
19620					idx: 1,
19621					type: "percent"
19622				},
19623				lightness: {
19624					idx: 2,
19625					type: "percent"
19626				}
19627			}
19628		}
19629	},
19630	propTypes = {
19631		"byte": {
19632			floor: true,
19633			max: 255
19634		},
19635		"percent": {
19636			max: 1
19637		},
19638		"degrees": {
19639			mod: 360,
19640			floor: true
19641		}
19642	},
19643	support = color.support = {},
19644
19645	// Element for support tests
19646	supportElem = jQuery( "<p>" )[ 0 ],
19647
19648	// Colors = jQuery.Color.names
19649	colors,
19650
19651	// Local aliases of functions called often
19652	each = jQuery.each;
19653
19654// Determine rgba support immediately
19655supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
19656support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;
19657
19658// Define cache name and alpha properties
19659// for rgba and hsla spaces
19660each( spaces, function( spaceName, space ) {
19661	space.cache = "_" + spaceName;
19662	space.props.alpha = {
19663		idx: 3,
19664		type: "percent",
19665		def: 1
19666	};
19667} );
19668
19669function clamp( value, prop, allowEmpty ) {
19670	var type = propTypes[ prop.type ] || {};
19671
19672	if ( value == null ) {
19673		return ( allowEmpty || !prop.def ) ? null : prop.def;
19674	}
19675
19676	// ~~ is an short way of doing floor for positive numbers
19677	value = type.floor ? ~~value : parseFloat( value );
19678
19679	// IE will pass in empty strings as value for alpha,
19680	// which will hit this case
19681	if ( isNaN( value ) ) {
19682		return prop.def;
19683	}
19684
19685	if ( type.mod ) {
19686
19687		// We add mod before modding to make sure that negatives values
19688		// get converted properly: -10 -> 350
19689		return ( value + type.mod ) % type.mod;
19690	}
19691
19692	// For now all property types without mod have min and max
19693	return 0 > value ? 0 : type.max < value ? type.max : value;
19694}
19695
19696function stringParse( string ) {
19697	var inst = color(),
19698		rgba = inst._rgba = [];
19699
19700	string = string.toLowerCase();
19701
19702	each( stringParsers, function( i, parser ) {
19703		var parsed,
19704			match = parser.re.exec( string ),
19705			values = match && parser.parse( match ),
19706			spaceName = parser.space || "rgba";
19707
19708		if ( values ) {
19709			parsed = inst[ spaceName ]( values );
19710
19711			// If this was an rgba parse the assignment might happen twice
19712			// oh well....
19713			inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
19714			rgba = inst._rgba = parsed._rgba;
19715
19716			// Exit each( stringParsers ) here because we matched
19717			return false;
19718		}
19719	} );
19720
19721	// Found a stringParser that handled it
19722	if ( rgba.length ) {
19723
19724		// If this came from a parsed string, force "transparent" when alpha is 0
19725		// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
19726		if ( rgba.join() === "0,0,0,0" ) {
19727			jQuery.extend( rgba, colors.transparent );
19728		}
19729		return inst;
19730	}
19731
19732	// Named colors
19733	return colors[ string ];
19734}
19735
19736color.fn = jQuery.extend( color.prototype, {
19737	parse: function( red, green, blue, alpha ) {
19738		if ( red === undefined ) {
19739			this._rgba = [ null, null, null, null ];
19740			return this;
19741		}
19742		if ( red.jquery || red.nodeType ) {
19743			red = jQuery( red ).css( green );
19744			green = undefined;
19745		}
19746
19747		var inst = this,
19748			type = jQuery.type( red ),
19749			rgba = this._rgba = [];
19750
19751		// More than 1 argument specified - assume ( red, green, blue, alpha )
19752		if ( green !== undefined ) {
19753			red = [ red, green, blue, alpha ];
19754			type = "array";
19755		}
19756
19757		if ( type === "string" ) {
19758			return this.parse( stringParse( red ) || colors._default );
19759		}
19760
19761		if ( type === "array" ) {
19762			each( spaces.rgba.props, function( key, prop ) {
19763				rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
19764			} );
19765			return this;
19766		}
19767
19768		if ( type === "object" ) {
19769			if ( red instanceof color ) {
19770				each( spaces, function( spaceName, space ) {
19771					if ( red[ space.cache ] ) {
19772						inst[ space.cache ] = red[ space.cache ].slice();
19773					}
19774				} );
19775			} else {
19776				each( spaces, function( spaceName, space ) {
19777					var cache = space.cache;
19778					each( space.props, function( key, prop ) {
19779
19780						// If the cache doesn't exist, and we know how to convert
19781						if ( !inst[ cache ] && space.to ) {
19782
19783							// If the value was null, we don't need to copy it
19784							// if the key was alpha, we don't need to copy it either
19785							if ( key === "alpha" || red[ key ] == null ) {
19786								return;
19787							}
19788							inst[ cache ] = space.to( inst._rgba );
19789						}
19790
19791						// This is the only case where we allow nulls for ALL properties.
19792						// call clamp with alwaysAllowEmpty
19793						inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
19794					} );
19795
19796					// Everything defined but alpha?
19797					if ( inst[ cache ] &&
19798							jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
19799
19800						// Use the default of 1
19801						inst[ cache ][ 3 ] = 1;
19802						if ( space.from ) {
19803							inst._rgba = space.from( inst[ cache ] );
19804						}
19805					}
19806				} );
19807			}
19808			return this;
19809		}
19810	},
19811	is: function( compare ) {
19812		var is = color( compare ),
19813			same = true,
19814			inst = this;
19815
19816		each( spaces, function( _, space ) {
19817			var localCache,
19818				isCache = is[ space.cache ];
19819			if ( isCache ) {
19820				localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
19821				each( space.props, function( _, prop ) {
19822					if ( isCache[ prop.idx ] != null ) {
19823						same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
19824						return same;
19825					}
19826				} );
19827			}
19828			return same;
19829		} );
19830		return same;
19831	},
19832	_space: function() {
19833		var used = [],
19834			inst = this;
19835		each( spaces, function( spaceName, space ) {
19836			if ( inst[ space.cache ] ) {
19837				used.push( spaceName );
19838			}
19839		} );
19840		return used.pop();
19841	},
19842	transition: function( other, distance ) {
19843		var end = color( other ),
19844			spaceName = end._space(),
19845			space = spaces[ spaceName ],
19846			startColor = this.alpha() === 0 ? color( "transparent" ) : this,
19847			start = startColor[ space.cache ] || space.to( startColor._rgba ),
19848			result = start.slice();
19849
19850		end = end[ space.cache ];
19851		each( space.props, function( key, prop ) {
19852			var index = prop.idx,
19853				startValue = start[ index ],
19854				endValue = end[ index ],
19855				type = propTypes[ prop.type ] || {};
19856
19857			// If null, don't override start value
19858			if ( endValue === null ) {
19859				return;
19860			}
19861
19862			// If null - use end
19863			if ( startValue === null ) {
19864				result[ index ] = endValue;
19865			} else {
19866				if ( type.mod ) {
19867					if ( endValue - startValue > type.mod / 2 ) {
19868						startValue += type.mod;
19869					} else if ( startValue - endValue > type.mod / 2 ) {
19870						startValue -= type.mod;
19871					}
19872				}
19873				result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
19874			}
19875		} );
19876		return this[ spaceName ]( result );
19877	},
19878	blend: function( opaque ) {
19879
19880		// If we are already opaque - return ourself
19881		if ( this._rgba[ 3 ] === 1 ) {
19882			return this;
19883		}
19884
19885		var rgb = this._rgba.slice(),
19886			a = rgb.pop(),
19887			blend = color( opaque )._rgba;
19888
19889		return color( jQuery.map( rgb, function( v, i ) {
19890			return ( 1 - a ) * blend[ i ] + a * v;
19891		} ) );
19892	},
19893	toRgbaString: function() {
19894		var prefix = "rgba(",
19895			rgba = jQuery.map( this._rgba, function( v, i ) {
19896				return v == null ? ( i > 2 ? 1 : 0 ) : v;
19897			} );
19898
19899		if ( rgba[ 3 ] === 1 ) {
19900			rgba.pop();
19901			prefix = "rgb(";
19902		}
19903
19904		return prefix + rgba.join() + ")";
19905	},
19906	toHslaString: function() {
19907		var prefix = "hsla(",
19908			hsla = jQuery.map( this.hsla(), function( v, i ) {
19909				if ( v == null ) {
19910					v = i > 2 ? 1 : 0;
19911				}
19912
19913				// Catch 1 and 2
19914				if ( i && i < 3 ) {
19915					v = Math.round( v * 100 ) + "%";
19916				}
19917				return v;
19918			} );
19919
19920		if ( hsla[ 3 ] === 1 ) {
19921			hsla.pop();
19922			prefix = "hsl(";
19923		}
19924		return prefix + hsla.join() + ")";
19925	},
19926	toHexString: function( includeAlpha ) {
19927		var rgba = this._rgba.slice(),
19928			alpha = rgba.pop();
19929
19930		if ( includeAlpha ) {
19931			rgba.push( ~~( alpha * 255 ) );
19932		}
19933
19934		return "#" + jQuery.map( rgba, function( v ) {
19935
19936			// Default to 0 when nulls exist
19937			v = ( v || 0 ).toString( 16 );
19938			return v.length === 1 ? "0" + v : v;
19939		} ).join( "" );
19940	},
19941	toString: function() {
19942		return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
19943	}
19944} );
19945color.fn.parse.prototype = color.fn;
19946
19947// Hsla conversions adapted from:
19948// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021
19949
19950function hue2rgb( p, q, h ) {
19951	h = ( h + 1 ) % 1;
19952	if ( h * 6 < 1 ) {
19953		return p + ( q - p ) * h * 6;
19954	}
19955	if ( h * 2 < 1 ) {
19956		return q;
19957	}
19958	if ( h * 3 < 2 ) {
19959		return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6;
19960	}
19961	return p;
19962}
19963
19964spaces.hsla.to = function( rgba ) {
19965	if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
19966		return [ null, null, null, rgba[ 3 ] ];
19967	}
19968	var r = rgba[ 0 ] / 255,
19969		g = rgba[ 1 ] / 255,
19970		b = rgba[ 2 ] / 255,
19971		a = rgba[ 3 ],
19972		max = Math.max( r, g, b ),
19973		min = Math.min( r, g, b ),
19974		diff = max - min,
19975		add = max + min,
19976		l = add * 0.5,
19977		h, s;
19978
19979	if ( min === max ) {
19980		h = 0;
19981	} else if ( r === max ) {
19982		h = ( 60 * ( g - b ) / diff ) + 360;
19983	} else if ( g === max ) {
19984		h = ( 60 * ( b - r ) / diff ) + 120;
19985	} else {
19986		h = ( 60 * ( r - g ) / diff ) + 240;
19987	}
19988
19989	// Chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
19990	// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
19991	if ( diff === 0 ) {
19992		s = 0;
19993	} else if ( l <= 0.5 ) {
19994		s = diff / add;
19995	} else {
19996		s = diff / ( 2 - add );
19997	}
19998	return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ];
19999};
20000
20001spaces.hsla.from = function( hsla ) {
20002	if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
20003		return [ null, null, null, hsla[ 3 ] ];
20004	}
20005	var h = hsla[ 0 ] / 360,
20006		s = hsla[ 1 ],
20007		l = hsla[ 2 ],
20008		a = hsla[ 3 ],
20009		q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
20010		p = 2 * l - q;
20011
20012	return [
20013		Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
20014		Math.round( hue2rgb( p, q, h ) * 255 ),
20015		Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
20016		a
20017	];
20018};
20019
20020each( spaces, function( spaceName, space ) {
20021	var props = space.props,
20022		cache = space.cache,
20023		to = space.to,
20024		from = space.from;
20025
20026	// Makes rgba() and hsla()
20027	color.fn[ spaceName ] = function( value ) {
20028
20029		// Generate a cache for this space if it doesn't exist
20030		if ( to && !this[ cache ] ) {
20031			this[ cache ] = to( this._rgba );
20032		}
20033		if ( value === undefined ) {
20034			return this[ cache ].slice();
20035		}
20036
20037		var ret,
20038			type = jQuery.type( value ),
20039			arr = ( type === "array" || type === "object" ) ? value : arguments,
20040			local = this[ cache ].slice();
20041
20042		each( props, function( key, prop ) {
20043			var val = arr[ type === "object" ? key : prop.idx ];
20044			if ( val == null ) {
20045				val = local[ prop.idx ];
20046			}
20047			local[ prop.idx ] = clamp( val, prop );
20048		} );
20049
20050		if ( from ) {
20051			ret = color( from( local ) );
20052			ret[ cache ] = local;
20053			return ret;
20054		} else {
20055			return color( local );
20056		}
20057	};
20058
20059	// Makes red() green() blue() alpha() hue() saturation() lightness()
20060	each( props, function( key, prop ) {
20061
20062		// Alpha is included in more than one space
20063		if ( color.fn[ key ] ) {
20064			return;
20065		}
20066		color.fn[ key ] = function( value ) {
20067			var vtype = jQuery.type( value ),
20068				fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
20069				local = this[ fn ](),
20070				cur = local[ prop.idx ],
20071				match;
20072
20073			if ( vtype === "undefined" ) {
20074				return cur;
20075			}
20076
20077			if ( vtype === "function" ) {
20078				value = value.call( this, cur );
20079				vtype = jQuery.type( value );
20080			}
20081			if ( value == null && prop.empty ) {
20082				return this;
20083			}
20084			if ( vtype === "string" ) {
20085				match = rplusequals.exec( value );
20086				if ( match ) {
20087					value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
20088				}
20089			}
20090			local[ prop.idx ] = value;
20091			return this[ fn ]( local );
20092		};
20093	} );
20094} );
20095
20096// Add cssHook and .fx.step function for each named hook.
20097// accept a space separated string of properties
20098color.hook = function( hook ) {
20099	var hooks = hook.split( " " );
20100	each( hooks, function( i, hook ) {
20101		jQuery.cssHooks[ hook ] = {
20102			set: function( elem, value ) {
20103				var parsed, curElem,
20104					backgroundColor = "";
20105
20106				if ( value !== "transparent" && ( jQuery.type( value ) !== "string" ||
20107						( parsed = stringParse( value ) ) ) ) {
20108					value = color( parsed || value );
20109					if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
20110						curElem = hook === "backgroundColor" ? elem.parentNode : elem;
20111						while (
20112							( backgroundColor === "" || backgroundColor === "transparent" ) &&
20113							curElem && curElem.style
20114						) {
20115							try {
20116								backgroundColor = jQuery.css( curElem, "backgroundColor" );
20117								curElem = curElem.parentNode;
20118							} catch ( e ) {
20119							}
20120						}
20121
20122						value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
20123							backgroundColor :
20124							"_default" );
20125					}
20126
20127					value = value.toRgbaString();
20128				}
20129				try {
20130					elem.style[ hook ] = value;
20131				} catch ( e ) {
20132
20133					// Wrapped to prevent IE from throwing errors on "invalid" values like
20134					// 'auto' or 'inherit'
20135				}
20136			}
20137		};
20138		jQuery.fx.step[ hook ] = function( fx ) {
20139			if ( !fx.colorInit ) {
20140				fx.start = color( fx.elem, hook );
20141				fx.end = color( fx.end );
20142				fx.colorInit = true;
20143			}
20144			jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
20145		};
20146	} );
20147
20148};
20149
20150color.hook( stepHooks );
20151
20152jQuery.cssHooks.borderColor = {
20153	expand: function( value ) {
20154		var expanded = {};
20155
20156		each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
20157			expanded[ "border" + part + "Color" ] = value;
20158		} );
20159		return expanded;
20160	}
20161};
20162
20163// Basic color names only.
20164// Usage of any of the other color names requires adding yourself or including
20165// jquery.color.svg-names.js.
20166colors = jQuery.Color.names = {
20167
20168	// 4.1. Basic color keywords
20169	aqua: "#00ffff",
20170	black: "#000000",
20171	blue: "#0000ff",
20172	fuchsia: "#ff00ff",
20173	gray: "#808080",
20174	green: "#008000",
20175	lime: "#00ff00",
20176	maroon: "#800000",
20177	navy: "#000080",
20178	olive: "#808000",
20179	purple: "#800080",
20180	red: "#ff0000",
20181	silver: "#c0c0c0",
20182	teal: "#008080",
20183	white: "#ffffff",
20184	yellow: "#ffff00",
20185
20186	// 4.2.3. "transparent" color keyword
20187	transparent: [ null, null, null, 0 ],
20188
20189	_default: "#ffffff"
20190};
20191
20192} )( jQuery );
20193
20194/******************************************************************************/
20195/****************************** CLASS ANIMATIONS ******************************/
20196/******************************************************************************/
20197( function() {
20198
20199var classAnimationActions = [ "add", "remove", "toggle" ],
20200	shorthandStyles = {
20201		border: 1,
20202		borderBottom: 1,
20203		borderColor: 1,
20204		borderLeft: 1,
20205		borderRight: 1,
20206		borderTop: 1,
20207		borderWidth: 1,
20208		margin: 1,
20209		padding: 1
20210	};
20211
20212$.each(
20213	[ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ],
20214	function( _, prop ) {
20215		$.fx.step[ prop ] = function( fx ) {
20216			if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
20217				jQuery.style( fx.elem, prop, fx.end );
20218				fx.setAttr = true;
20219			}
20220		};
20221	}
20222);
20223
20224function getElementStyles( elem ) {
20225	var key, len,
20226		style = elem.ownerDocument.defaultView ?
20227			elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
20228			elem.currentStyle,
20229		styles = {};
20230
20231	if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
20232		len = style.length;
20233		while ( len-- ) {
20234			key = style[ len ];
20235			if ( typeof style[ key ] === "string" ) {
20236				styles[ $.camelCase( key ) ] = style[ key ];
20237			}
20238		}
20239
20240	// Support: Opera, IE <9
20241	} else {
20242		for ( key in style ) {
20243			if ( typeof style[ key ] === "string" ) {
20244				styles[ key ] = style[ key ];
20245			}
20246		}
20247	}
20248
20249	return styles;
20250}
20251
20252function styleDifference( oldStyle, newStyle ) {
20253	var diff = {},
20254		name, value;
20255
20256	for ( name in newStyle ) {
20257		value = newStyle[ name ];
20258		if ( oldStyle[ name ] !== value ) {
20259			if ( !shorthandStyles[ name ] ) {
20260				if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
20261					diff[ name ] = value;
20262				}
20263			}
20264		}
20265	}
20266
20267	return diff;
20268}
20269
20270// Support: jQuery <1.8
20271if ( !$.fn.addBack ) {
20272	$.fn.addBack = function( selector ) {
20273		return this.add( selector == null ?
20274			this.prevObject : this.prevObject.filter( selector )
20275		);
20276	};
20277}
20278
20279$.effects.animateClass = function( value, duration, easing, callback ) {
20280	var o = $.speed( duration, easing, callback );
20281
20282	return this.queue( function() {
20283		var animated = $( this ),
20284			baseClass = animated.attr( "class" ) || "",
20285			applyClassChange,
20286			allAnimations = o.children ? animated.find( "*" ).addBack() : animated;
20287
20288		// Map the animated objects to store the original styles.
20289		allAnimations = allAnimations.map( function() {
20290			var el = $( this );
20291			return {
20292				el: el,
20293				start: getElementStyles( this )
20294			};
20295		} );
20296
20297		// Apply class change
20298		applyClassChange = function() {
20299			$.each( classAnimationActions, function( i, action ) {
20300				if ( value[ action ] ) {
20301					animated[ action + "Class" ]( value[ action ] );
20302				}
20303			} );
20304		};
20305		applyClassChange();
20306
20307		// Map all animated objects again - calculate new styles and diff
20308		allAnimations = allAnimations.map( function() {
20309			this.end = getElementStyles( this.el[ 0 ] );
20310			this.diff = styleDifference( this.start, this.end );
20311			return this;
20312		} );
20313
20314		// Apply original class
20315		animated.attr( "class", baseClass );
20316
20317		// Map all animated objects again - this time collecting a promise
20318		allAnimations = allAnimations.map( function() {
20319			var styleInfo = this,
20320				dfd = $.Deferred(),
20321				opts = $.extend( {}, o, {
20322					queue: false,
20323					complete: function() {
20324						dfd.resolve( styleInfo );
20325					}
20326				} );
20327
20328			this.el.animate( this.diff, opts );
20329			return dfd.promise();
20330		} );
20331
20332		// Once all animations have completed:
20333		$.when.apply( $, allAnimations.get() ).done( function() {
20334
20335			// Set the final class
20336			applyClassChange();
20337
20338			// For each animated element,
20339			// clear all css properties that were animated
20340			$.each( arguments, function() {
20341				var el = this.el;
20342				$.each( this.diff, function( key ) {
20343					el.css( key, "" );
20344				} );
20345			} );
20346
20347			// This is guarnteed to be there if you use jQuery.speed()
20348			// it also handles dequeuing the next anim...
20349			o.complete.call( animated[ 0 ] );
20350		} );
20351	} );
20352};
20353
20354$.fn.extend( {
20355	addClass: ( function( orig ) {
20356		return function( classNames, speed, easing, callback ) {
20357			return speed ?
20358				$.effects.animateClass.call( this,
20359					{ add: classNames }, speed, easing, callback ) :
20360				orig.apply( this, arguments );
20361		};
20362	} )( $.fn.addClass ),
20363
20364	removeClass: ( function( orig ) {
20365		return function( classNames, speed, easing, callback ) {
20366			return arguments.length > 1 ?
20367				$.effects.animateClass.call( this,
20368					{ remove: classNames }, speed, easing, callback ) :
20369				orig.apply( this, arguments );
20370		};
20371	} )( $.fn.removeClass ),
20372
20373	toggleClass: ( function( orig ) {
20374		return function( classNames, force, speed, easing, callback ) {
20375			if ( typeof force === "boolean" || force === undefined ) {
20376				if ( !speed ) {
20377
20378					// Without speed parameter
20379					return orig.apply( this, arguments );
20380				} else {
20381					return $.effects.animateClass.call( this,
20382						( force ? { add: classNames } : { remove: classNames } ),
20383						speed, easing, callback );
20384				}
20385			} else {
20386
20387				// Without force parameter
20388				return $.effects.animateClass.call( this,
20389					{ toggle: classNames }, force, speed, easing );
20390			}
20391		};
20392	} )( $.fn.toggleClass ),
20393
20394	switchClass: function( remove, add, speed, easing, callback ) {
20395		return $.effects.animateClass.call( this, {
20396			add: add,
20397			remove: remove
20398		}, speed, easing, callback );
20399	}
20400} );
20401
20402} )();
20403
20404/******************************************************************************/
20405/*********************************** EFFECTS **********************************/
20406/******************************************************************************/
20407
20408( function() {
20409
20410if ( $.expr && $.expr.filters && $.expr.filters.animated ) {
20411	$.expr.filters.animated = ( function( orig ) {
20412		return function( elem ) {
20413			return !!$( elem ).data( dataSpaceAnimated ) || orig( elem );
20414		};
20415	} )( $.expr.filters.animated );
20416}
20417
20418if ( $.uiBackCompat !== false ) {
20419	$.extend( $.effects, {
20420
20421		// Saves a set of properties in a data storage
20422		save: function( element, set ) {
20423			var i = 0, length = set.length;
20424			for ( ; i < length; i++ ) {
20425				if ( set[ i ] !== null ) {
20426					element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
20427				}
20428			}
20429		},
20430
20431		// Restores a set of previously saved properties from a data storage
20432		restore: function( element, set ) {
20433			var val, i = 0, length = set.length;
20434			for ( ; i < length; i++ ) {
20435				if ( set[ i ] !== null ) {
20436					val = element.data( dataSpace + set[ i ] );
20437					element.css( set[ i ], val );
20438				}
20439			}
20440		},
20441
20442		setMode: function( el, mode ) {
20443			if ( mode === "toggle" ) {
20444				mode = el.is( ":hidden" ) ? "show" : "hide";
20445			}
20446			return mode;
20447		},
20448
20449		// Wraps the element around a wrapper that copies position properties
20450		createWrapper: function( element ) {
20451
20452			// If the element is already wrapped, return it
20453			if ( element.parent().is( ".ui-effects-wrapper" ) ) {
20454				return element.parent();
20455			}
20456
20457			// Wrap the element
20458			var props = {
20459					width: element.outerWidth( true ),
20460					height: element.outerHeight( true ),
20461					"float": element.css( "float" )
20462				},
20463				wrapper = $( "<div></div>" )
20464					.addClass( "ui-effects-wrapper" )
20465					.css( {
20466						fontSize: "100%",
20467						background: "transparent",
20468						border: "none",
20469						margin: 0,
20470						padding: 0
20471					} ),
20472
20473				// Store the size in case width/height are defined in % - Fixes #5245
20474				size = {
20475					width: element.width(),
20476					height: element.height()
20477				},
20478				active = document.activeElement;
20479
20480			// Support: Firefox
20481			// Firefox incorrectly exposes anonymous content
20482			// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
20483			try {
20484				active.id;
20485			} catch ( e ) {
20486				active = document.body;
20487			}
20488
20489			element.wrap( wrapper );
20490
20491			// Fixes #7595 - Elements lose focus when wrapped.
20492			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
20493				$( active ).trigger( "focus" );
20494			}
20495
20496			// Hotfix for jQuery 1.4 since some change in wrap() seems to actually
20497			// lose the reference to the wrapped element
20498			wrapper = element.parent();
20499
20500			// Transfer positioning properties to the wrapper
20501			if ( element.css( "position" ) === "static" ) {
20502				wrapper.css( { position: "relative" } );
20503				element.css( { position: "relative" } );
20504			} else {
20505				$.extend( props, {
20506					position: element.css( "position" ),
20507					zIndex: element.css( "z-index" )
20508				} );
20509				$.each( [ "top", "left", "bottom", "right" ], function( i, pos ) {
20510					props[ pos ] = element.css( pos );
20511					if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
20512						props[ pos ] = "auto";
20513					}
20514				} );
20515				element.css( {
20516					position: "relative",
20517					top: 0,
20518					left: 0,
20519					right: "auto",
20520					bottom: "auto"
20521				} );
20522			}
20523			element.css( size );
20524
20525			return wrapper.css( props ).show();
20526		},
20527
20528		removeWrapper: function( element ) {
20529			var active = document.activeElement;
20530
20531			if ( element.parent().is( ".ui-effects-wrapper" ) ) {
20532				element.parent().replaceWith( element );
20533
20534				// Fixes #7595 - Elements lose focus when wrapped.
20535				if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
20536					$( active ).trigger( "focus" );
20537				}
20538			}
20539
20540			return element;
20541		}
20542	} );
20543}
20544
20545$.extend( $.effects, {
20546	version: "1.12.1",
20547
20548	define: function( name, mode, effect ) {
20549		if ( !effect ) {
20550			effect = mode;
20551			mode = "effect";
20552		}
20553
20554		$.effects.effect[ name ] = effect;
20555		$.effects.effect[ name ].mode = mode;
20556
20557		return effect;
20558	},
20559
20560	scaledDimensions: function( element, percent, direction ) {
20561		if ( percent === 0 ) {
20562			return {
20563				height: 0,
20564				width: 0,
20565				outerHeight: 0,
20566				outerWidth: 0
20567			};
20568		}
20569
20570		var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1,
20571			y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1;
20572
20573		return {
20574			height: element.height() * y,
20575			width: element.width() * x,
20576			outerHeight: element.outerHeight() * y,
20577			outerWidth: element.outerWidth() * x
20578		};
20579
20580	},
20581
20582	clipToBox: function( animation ) {
20583		return {
20584			width: animation.clip.right - animation.clip.left,
20585			height: animation.clip.bottom - animation.clip.top,
20586			left: animation.clip.left,
20587			top: animation.clip.top
20588		};
20589	},
20590
20591	// Injects recently queued functions to be first in line (after "inprogress")
20592	unshift: function( element, queueLength, count ) {
20593		var queue = element.queue();
20594
20595		if ( queueLength > 1 ) {
20596			queue.splice.apply( queue,
20597				[ 1, 0 ].concat( queue.splice( queueLength, count ) ) );
20598		}
20599		element.dequeue();
20600	},
20601
20602	saveStyle: function( element ) {
20603		element.data( dataSpaceStyle, element[ 0 ].style.cssText );
20604	},
20605
20606	restoreStyle: function( element ) {
20607		element[ 0 ].style.cssText = element.data( dataSpaceStyle ) || "";
20608		element.removeData( dataSpaceStyle );
20609	},
20610
20611	mode: function( element, mode ) {
20612		var hidden = element.is( ":hidden" );
20613
20614		if ( mode === "toggle" ) {
20615			mode = hidden ? "show" : "hide";
20616		}
20617		if ( hidden ? mode === "hide" : mode === "show" ) {
20618			mode = "none";
20619		}
20620		return mode;
20621	},
20622
20623	// Translates a [top,left] array into a baseline value
20624	getBaseline: function( origin, original ) {
20625		var y, x;
20626
20627		switch ( origin[ 0 ] ) {
20628		case "top":
20629			y = 0;
20630			break;
20631		case "middle":
20632			y = 0.5;
20633			break;
20634		case "bottom":
20635			y = 1;
20636			break;
20637		default:
20638			y = origin[ 0 ] / original.height;
20639		}
20640
20641		switch ( origin[ 1 ] ) {
20642		case "left":
20643			x = 0;
20644			break;
20645		case "center":
20646			x = 0.5;
20647			break;
20648		case "right":
20649			x = 1;
20650			break;
20651		default:
20652			x = origin[ 1 ] / original.width;
20653		}
20654
20655		return {
20656			x: x,
20657			y: y
20658		};
20659	},
20660
20661	// Creates a placeholder element so that the original element can be made absolute
20662	createPlaceholder: function( element ) {
20663		var placeholder,
20664			cssPosition = element.css( "position" ),
20665			position = element.position();
20666
20667		// Lock in margins first to account for form elements, which
20668		// will change margin if you explicitly set height
20669		// see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380
20670		// Support: Safari
20671		element.css( {
20672			marginTop: element.css( "marginTop" ),
20673			marginBottom: element.css( "marginBottom" ),
20674			marginLeft: element.css( "marginLeft" ),
20675			marginRight: element.css( "marginRight" )
20676		} )
20677		.outerWidth( element.outerWidth() )
20678		.outerHeight( element.outerHeight() );
20679
20680		if ( /^(static|relative)/.test( cssPosition ) ) {
20681			cssPosition = "absolute";
20682
20683			placeholder = $( "<" + element[ 0 ].nodeName + ">" ).insertAfter( element ).css( {
20684
20685				// Convert inline to inline block to account for inline elements
20686				// that turn to inline block based on content (like img)
20687				display: /^(inline|ruby)/.test( element.css( "display" ) ) ?
20688					"inline-block" :
20689					"block",
20690				visibility: "hidden",
20691
20692				// Margins need to be set to account for margin collapse
20693				marginTop: element.css( "marginTop" ),
20694				marginBottom: element.css( "marginBottom" ),
20695				marginLeft: element.css( "marginLeft" ),
20696				marginRight: element.css( "marginRight" ),
20697				"float": element.css( "float" )
20698			} )
20699			.outerWidth( element.outerWidth() )
20700			.outerHeight( element.outerHeight() )
20701			.addClass( "ui-effects-placeholder" );
20702
20703			element.data( dataSpace + "placeholder", placeholder );
20704		}
20705
20706		element.css( {
20707			position: cssPosition,
20708			left: position.left,
20709			top: position.top
20710		} );
20711
20712		return placeholder;
20713	},
20714
20715	removePlaceholder: function( element ) {
20716		var dataKey = dataSpace + "placeholder",
20717				placeholder = element.data( dataKey );
20718
20719		if ( placeholder ) {
20720			placeholder.remove();
20721			element.removeData( dataKey );
20722		}
20723	},
20724
20725	// Removes a placeholder if it exists and restores
20726	// properties that were modified during placeholder creation
20727	cleanUp: function( element ) {
20728		$.effects.restoreStyle( element );
20729		$.effects.removePlaceholder( element );
20730	},
20731
20732	setTransition: function( element, list, factor, value ) {
20733		value = value || {};
20734		$.each( list, function( i, x ) {
20735			var unit = element.cssUnit( x );
20736			if ( unit[ 0 ] > 0 ) {
20737				value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
20738			}
20739		} );
20740		return value;
20741	}
20742} );
20743
20744// Return an effect options object for the given parameters:
20745function _normalizeArguments( effect, options, speed, callback ) {
20746
20747	// Allow passing all options as the first parameter
20748	if ( $.isPlainObject( effect ) ) {
20749		options = effect;
20750		effect = effect.effect;
20751	}
20752
20753	// Convert to an object
20754	effect = { effect: effect };
20755
20756	// Catch (effect, null, ...)
20757	if ( options == null ) {
20758		options = {};
20759	}
20760
20761	// Catch (effect, callback)
20762	if ( $.isFunction( options ) ) {
20763		callback = options;
20764		speed = null;
20765		options = {};
20766	}
20767
20768	// Catch (effect, speed, ?)
20769	if ( typeof options === "number" || $.fx.speeds[ options ] ) {
20770		callback = speed;
20771		speed = options;
20772		options = {};
20773	}
20774
20775	// Catch (effect, options, callback)
20776	if ( $.isFunction( speed ) ) {
20777		callback = speed;
20778		speed = null;
20779	}
20780
20781	// Add options to effect
20782	if ( options ) {
20783		$.extend( effect, options );
20784	}
20785
20786	speed = speed || options.duration;
20787	effect.duration = $.fx.off ? 0 :
20788		typeof speed === "number" ? speed :
20789		speed in $.fx.speeds ? $.fx.speeds[ speed ] :
20790		$.fx.speeds._default;
20791
20792	effect.complete = callback || options.complete;
20793
20794	return effect;
20795}
20796
20797function standardAnimationOption( option ) {
20798
20799	// Valid standard speeds (nothing, number, named speed)
20800	if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) {
20801		return true;
20802	}
20803
20804	// Invalid strings - treat as "normal" speed
20805	if ( typeof option === "string" && !$.effects.effect[ option ] ) {
20806		return true;
20807	}
20808
20809	// Complete callback
20810	if ( $.isFunction( option ) ) {
20811		return true;
20812	}
20813
20814	// Options hash (but not naming an effect)
20815	if ( typeof option === "object" && !option.effect ) {
20816		return true;
20817	}
20818
20819	// Didn't match any standard API
20820	return false;
20821}
20822
20823$.fn.extend( {
20824	effect: function( /* effect, options, speed, callback */ ) {
20825		var args = _normalizeArguments.apply( this, arguments ),
20826			effectMethod = $.effects.effect[ args.effect ],
20827			defaultMode = effectMethod.mode,
20828			queue = args.queue,
20829			queueName = queue || "fx",
20830			complete = args.complete,
20831			mode = args.mode,
20832			modes = [],
20833			prefilter = function( next ) {
20834				var el = $( this ),
20835					normalizedMode = $.effects.mode( el, mode ) || defaultMode;
20836
20837				// Sentinel for duck-punching the :animated psuedo-selector
20838				el.data( dataSpaceAnimated, true );
20839
20840				// Save effect mode for later use,
20841				// we can't just call $.effects.mode again later,
20842				// as the .show() below destroys the initial state
20843				modes.push( normalizedMode );
20844
20845				// See $.uiBackCompat inside of run() for removal of defaultMode in 1.13
20846				if ( defaultMode && ( normalizedMode === "show" ||
20847						( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) {
20848					el.show();
20849				}
20850
20851				if ( !defaultMode || normalizedMode !== "none" ) {
20852					$.effects.saveStyle( el );
20853				}
20854
20855				if ( $.isFunction( next ) ) {
20856					next();
20857				}
20858			};
20859
20860		if ( $.fx.off || !effectMethod ) {
20861
20862			// Delegate to the original method (e.g., .show()) if possible
20863			if ( mode ) {
20864				return this[ mode ]( args.duration, complete );
20865			} else {
20866				return this.each( function() {
20867					if ( complete ) {
20868						complete.call( this );
20869					}
20870				} );
20871			}
20872		}
20873
20874		function run( next ) {
20875			var elem = $( this );
20876
20877			function cleanup() {
20878				elem.removeData( dataSpaceAnimated );
20879
20880				$.effects.cleanUp( elem );
20881
20882				if ( args.mode === "hide" ) {
20883					elem.hide();
20884				}
20885
20886				done();
20887			}
20888
20889			function done() {
20890				if ( $.isFunction( complete ) ) {
20891					complete.call( elem[ 0 ] );
20892				}
20893
20894				if ( $.isFunction( next ) ) {
20895					next();
20896				}
20897			}
20898
20899			// Override mode option on a per element basis,
20900			// as toggle can be either show or hide depending on element state
20901			args.mode = modes.shift();
20902
20903			if ( $.uiBackCompat !== false && !defaultMode ) {
20904				if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
20905
20906					// Call the core method to track "olddisplay" properly
20907					elem[ mode ]();
20908					done();
20909				} else {
20910					effectMethod.call( elem[ 0 ], args, done );
20911				}
20912			} else {
20913				if ( args.mode === "none" ) {
20914
20915					// Call the core method to track "olddisplay" properly
20916					elem[ mode ]();
20917					done();
20918				} else {
20919					effectMethod.call( elem[ 0 ], args, cleanup );
20920				}
20921			}
20922		}
20923
20924		// Run prefilter on all elements first to ensure that
20925		// any showing or hiding happens before placeholder creation,
20926		// which ensures that any layout changes are correctly captured.
20927		return queue === false ?
20928			this.each( prefilter ).each( run ) :
20929			this.queue( queueName, prefilter ).queue( queueName, run );
20930	},
20931
20932	show: ( function( orig ) {
20933		return function( option ) {
20934			if ( standardAnimationOption( option ) ) {
20935				return orig.apply( this, arguments );
20936			} else {
20937				var args = _normalizeArguments.apply( this, arguments );
20938				args.mode = "show";
20939				return this.effect.call( this, args );
20940			}
20941		};
20942	} )( $.fn.show ),
20943
20944	hide: ( function( orig ) {
20945		return function( option ) {
20946			if ( standardAnimationOption( option ) ) {
20947				return orig.apply( this, arguments );
20948			} else {
20949				var args = _normalizeArguments.apply( this, arguments );
20950				args.mode = "hide";
20951				return this.effect.call( this, args );
20952			}
20953		};
20954	} )( $.fn.hide ),
20955
20956	toggle: ( function( orig ) {
20957		return function( option ) {
20958			if ( standardAnimationOption( option ) || typeof option === "boolean" ) {
20959				return orig.apply( this, arguments );
20960			} else {
20961				var args = _normalizeArguments.apply( this, arguments );
20962				args.mode = "toggle";
20963				return this.effect.call( this, args );
20964			}
20965		};
20966	} )( $.fn.toggle ),
20967
20968	cssUnit: function( key ) {
20969		var style = this.css( key ),
20970			val = [];
20971
20972		$.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
20973			if ( style.indexOf( unit ) > 0 ) {
20974				val = [ parseFloat( style ), unit ];
20975			}
20976		} );
20977		return val;
20978	},
20979
20980	cssClip: function( clipObj ) {
20981		if ( clipObj ) {
20982			return this.css( "clip", "rect(" + clipObj.top + "px " + clipObj.right + "px " +
20983				clipObj.bottom + "px " + clipObj.left + "px)" );
20984		}
20985		return parseClip( this.css( "clip" ), this );
20986	},
20987
20988	transfer: function( options, done ) {
20989		var element = $( this ),
20990			target = $( options.to ),
20991			targetFixed = target.css( "position" ) === "fixed",
20992			body = $( "body" ),
20993			fixTop = targetFixed ? body.scrollTop() : 0,
20994			fixLeft = targetFixed ? body.scrollLeft() : 0,
20995			endPosition = target.offset(),
20996			animation = {
20997				top: endPosition.top - fixTop,
20998				left: endPosition.left - fixLeft,
20999				height: target.innerHeight(),
21000				width: target.innerWidth()
21001			},
21002			startPosition = element.offset(),
21003			transfer = $( "<div class='ui-effects-transfer'></div>" )
21004				.appendTo( "body" )
21005				.addClass( options.className )
21006				.css( {
21007					top: startPosition.top - fixTop,
21008					left: startPosition.left - fixLeft,
21009					height: element.innerHeight(),
21010					width: element.innerWidth(),
21011					position: targetFixed ? "fixed" : "absolute"
21012				} )
21013				.animate( animation, options.duration, options.easing, function() {
21014					transfer.remove();
21015					if ( $.isFunction( done ) ) {
21016						done();
21017					}
21018				} );
21019	}
21020} );
21021
21022function parseClip( str, element ) {
21023		var outerWidth = element.outerWidth(),
21024			outerHeight = element.outerHeight(),
21025			clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,
21026			values = clipRegex.exec( str ) || [ "", 0, outerWidth, outerHeight, 0 ];
21027
21028		return {
21029			top: parseFloat( values[ 1 ] ) || 0,
21030			right: values[ 2 ] === "auto" ? outerWidth : parseFloat( values[ 2 ] ),
21031			bottom: values[ 3 ] === "auto" ? outerHeight : parseFloat( values[ 3 ] ),
21032			left: parseFloat( values[ 4 ] ) || 0
21033		};
21034}
21035
21036$.fx.step.clip = function( fx ) {
21037	if ( !fx.clipInit ) {
21038		fx.start = $( fx.elem ).cssClip();
21039		if ( typeof fx.end === "string" ) {
21040			fx.end = parseClip( fx.end, fx.elem );
21041		}
21042		fx.clipInit = true;
21043	}
21044
21045	$( fx.elem ).cssClip( {
21046		top: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top,
21047		right: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right,
21048		bottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom,
21049		left: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left
21050	} );
21051};
21052
21053} )();
21054
21055/******************************************************************************/
21056/*********************************** EASING ***********************************/
21057/******************************************************************************/
21058
21059( function() {
21060
21061// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
21062
21063var baseEasings = {};
21064
21065$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
21066	baseEasings[ name ] = function( p ) {
21067		return Math.pow( p, i + 2 );
21068	};
21069} );
21070
21071$.extend( baseEasings, {
21072	Sine: function( p ) {
21073		return 1 - Math.cos( p * Math.PI / 2 );
21074	},
21075	Circ: function( p ) {
21076		return 1 - Math.sqrt( 1 - p * p );
21077	},
21078	Elastic: function( p ) {
21079		return p === 0 || p === 1 ? p :
21080			-Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 );
21081	},
21082	Back: function( p ) {
21083		return p * p * ( 3 * p - 2 );
21084	},
21085	Bounce: function( p ) {
21086		var pow2,
21087			bounce = 4;
21088
21089		while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
21090		return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
21091	}
21092} );
21093
21094$.each( baseEasings, function( name, easeIn ) {
21095	$.easing[ "easeIn" + name ] = easeIn;
21096	$.easing[ "easeOut" + name ] = function( p ) {
21097		return 1 - easeIn( 1 - p );
21098	};
21099	$.easing[ "easeInOut" + name ] = function( p ) {
21100		return p < 0.5 ?
21101			easeIn( p * 2 ) / 2 :
21102			1 - easeIn( p * -2 + 2 ) / 2;
21103	};
21104} );
21105
21106} )();
21107
21108return $.effects;
21109
21110} ) );
21111
21112
21113
21114/*!
21115 * jQuery UI Form Reset Mixin 1.12.1
21116 * http://jqueryui.com
21117 *
21118 * Copyright jQuery Foundation and other contributors
21119 * Released under the MIT license.
21120 * http://jquery.org/license
21121 */
21122
21123//>>label: Form Reset Mixin
21124//>>group: Core
21125//>>description: Refresh input widgets when their form is reset
21126//>>docs: http://api.jqueryui.com/form-reset-mixin/
21127
21128( function( factory ) {
21129	if ( typeof define === "function" && define.amd ) {
21130
21131		// AMD. Register as an anonymous module.
21132		define( [
21133			"jquery",
21134			"./form",
21135			"./version"
21136		], factory );
21137	} else {
21138
21139		// Browser globals
21140		factory( jQuery );
21141	}
21142}( function( $ ) {
21143
21144return $.ui.formResetMixin = {
21145	_formResetHandler: function() {
21146		var form = $( this );
21147
21148		// Wait for the form reset to actually happen before refreshing
21149		setTimeout( function() {
21150			var instances = form.data( "ui-form-reset-instances" );
21151			$.each( instances, function() {
21152				this.refresh();
21153			} );
21154		} );
21155	},
21156
21157	_bindFormResetHandler: function() {
21158		this.form = this.element.form();
21159		if ( !this.form.length ) {
21160			return;
21161		}
21162
21163		var instances = this.form.data( "ui-form-reset-instances" ) || [];
21164		if ( !instances.length ) {
21165
21166			// We don't use _on() here because we use a single event handler per form
21167			this.form.on( "reset.ui-form-reset", this._formResetHandler );
21168		}
21169		instances.push( this );
21170		this.form.data( "ui-form-reset-instances", instances );
21171	},
21172
21173	_unbindFormResetHandler: function() {
21174		if ( !this.form.length ) {
21175			return;
21176		}
21177
21178		var instances = this.form.data( "ui-form-reset-instances" );
21179		instances.splice( $.inArray( this, instances ), 1 );
21180		if ( instances.length ) {
21181			this.form.data( "ui-form-reset-instances", instances );
21182		} else {
21183			this.form
21184				.removeData( "ui-form-reset-instances" )
21185				.off( "reset.ui-form-reset" );
21186		}
21187	}
21188};
21189
21190} ) );
21191
21192
21193/*!
21194 * jQuery UI Position 1.12.1
21195 * http://jqueryui.com
21196 *
21197 * Copyright jQuery Foundation and other contributors
21198 * Released under the MIT license.
21199 * http://jquery.org/license
21200 *
21201 * http://api.jqueryui.com/position/
21202 */
21203
21204//>>label: Position
21205//>>group: Core
21206//>>description: Positions elements relative to other elements.
21207//>>docs: http://api.jqueryui.com/position/
21208//>>demos: http://jqueryui.com/position/
21209
21210( function( factory ) {
21211	if ( typeof define === "function" && define.amd ) {
21212
21213		// AMD. Register as an anonymous module.
21214		define( [ "jquery", "./version" ], factory );
21215	} else {
21216
21217		// Browser globals
21218		factory( jQuery );
21219	}
21220}( function( $ ) {
vendor: 14,195 bytes, lines 21220-21687
21220
21221( function() {
21222var cachedScrollbarWidth,
21223	max = Math.max,
21224	abs = Math.abs,
21225	rhorizontal = /left|center|right/,
21226	rvertical = /top|center|bottom/,
21227	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
21228	rposition = /^\w+/,
21229	rpercent = /%$/,
21230	_position = $.fn.position;
21231
21232function getOffsets( offsets, width, height ) {
21233	return [
21234		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
21235		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
21236	];
21237}
21238
21239function parseCss( element, property ) {
21240	return parseInt( $.css( element, property ), 10 ) || 0;
21241}
21242
21243function getDimensions( elem ) {
21244	var raw = elem[ 0 ];
21245	if ( raw.nodeType === 9 ) {
21246		return {
21247			width: elem.width(),
21248			height: elem.height(),
21249			offset: { top: 0, left: 0 }
21250		};
21251	}
21252	if ( $.isWindow( raw ) ) {
21253		return {
21254			width: elem.width(),
21255			height: elem.height(),
21256			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
21257		};
21258	}
21259	if ( raw.preventDefault ) {
21260		return {
21261			width: 0,
21262			height: 0,
21263			offset: { top: raw.pageY, left: raw.pageX }
21264		};
21265	}
21266	return {
21267		width: elem.outerWidth(),
21268		height: elem.outerHeight(),
21269		offset: elem.offset()
21270	};
21271}
21272
21273$.position = {
21274	scrollbarWidth: function() {
21275		if ( cachedScrollbarWidth !== undefined ) {
21276			return cachedScrollbarWidth;
21277		}
21278		var w1, w2,
21279			div = $( "<div " +
21280				"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
21281				"<div style='height:100px;width:auto;'></div></div>" ),
21282			innerDiv = div.children()[ 0 ];
21283
21284		$( "body" ).append( div );
21285		w1 = innerDiv.offsetWidth;
21286		div.css( "overflow", "scroll" );
21287
21288		w2 = innerDiv.offsetWidth;
21289
21290		if ( w1 === w2 ) {
21291			w2 = div[ 0 ].clientWidth;
21292		}
21293
21294		div.remove();
21295
21296		return ( cachedScrollbarWidth = w1 - w2 );
21297	},
21298	getScrollInfo: function( within ) {
21299		var overflowX = within.isWindow || within.isDocument ? "" :
21300				within.element.css( "overflow-x" ),
21301			overflowY = within.isWindow || within.isDocument ? "" :
21302				within.element.css( "overflow-y" ),
21303			hasOverflowX = overflowX === "scroll" ||
21304				( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
21305			hasOverflowY = overflowY === "scroll" ||
21306				( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
21307		return {
21308			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
21309			height: hasOverflowX ? $.position.scrollbarWidth() : 0
21310		};
21311	},
21312	getWithinInfo: function( element ) {
21313		var withinElement = $( element || window ),
21314			isWindow = $.isWindow( withinElement[ 0 ] ),
21315			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
21316			hasOffset = !isWindow && !isDocument;
21317		return {
21318			element: withinElement,
21319			isWindow: isWindow,
21320			isDocument: isDocument,
21321			offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
21322			scrollLeft: withinElement.scrollLeft(),
21323			scrollTop: withinElement.scrollTop(),
21324			width: withinElement.outerWidth(),
21325			height: withinElement.outerHeight()
21326		};
21327	}
21328};
21329
21330$.fn.position = function( options ) {
21331	if ( !options || !options.of ) {
21332		return _position.apply( this, arguments );
21333	}
21334
21335	// Make a copy, we don't want to modify arguments
21336	options = $.extend( {}, options );
21337
21338	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
21339		target = $( options.of ),
21340		within = $.position.getWithinInfo( options.within ),
21341		scrollInfo = $.position.getScrollInfo( within ),
21342		collision = ( options.collision || "flip" ).split( " " ),
21343		offsets = {};
21344
21345	dimensions = getDimensions( target );
21346	if ( target[ 0 ].preventDefault ) {
21347
21348		// Force left top to allow flipping
21349		options.at = "left top";
21350	}
21351	targetWidth = dimensions.width;
21352	targetHeight = dimensions.height;
21353	targetOffset = dimensions.offset;
21354
21355	// Clone to reuse original targetOffset later
21356	basePosition = $.extend( {}, targetOffset );
21357
21358	// Force my and at to have valid horizontal and vertical positions
21359	// if a value is missing or invalid, it will be converted to center
21360	$.each( [ "my", "at" ], function() {
21361		var pos = ( options[ this ] || "" ).split( " " ),
21362			horizontalOffset,
21363			verticalOffset;
21364
21365		if ( pos.length === 1 ) {
21366			pos = rhorizontal.test( pos[ 0 ] ) ?
21367				pos.concat( [ "center" ] ) :
21368				rvertical.test( pos[ 0 ] ) ?
21369					[ "center" ].concat( pos ) :
21370					[ "center", "center" ];
21371		}
21372		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
21373		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
21374
21375		// Calculate offsets
21376		horizontalOffset = roffset.exec( pos[ 0 ] );
21377		verticalOffset = roffset.exec( pos[ 1 ] );
21378		offsets[ this ] = [
21379			horizontalOffset ? horizontalOffset[ 0 ] : 0,
21380			verticalOffset ? verticalOffset[ 0 ] : 0
21381		];
21382
21383		// Reduce to just the positions without the offsets
21384		options[ this ] = [
21385			rposition.exec( pos[ 0 ] )[ 0 ],
21386			rposition.exec( pos[ 1 ] )[ 0 ]
21387		];
21388	} );
21389
21390	// Normalize collision option
21391	if ( collision.length === 1 ) {
21392		collision[ 1 ] = collision[ 0 ];
21393	}
21394
21395	if ( options.at[ 0 ] === "right" ) {
21396		basePosition.left += targetWidth;
21397	} else if ( options.at[ 0 ] === "center" ) {
21398		basePosition.left += targetWidth / 2;
21399	}
21400
21401	if ( options.at[ 1 ] === "bottom" ) {
21402		basePosition.top += targetHeight;
21403	} else if ( options.at[ 1 ] === "center" ) {
21404		basePosition.top += targetHeight / 2;
21405	}
21406
21407	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
21408	basePosition.left += atOffset[ 0 ];
21409	basePosition.top += atOffset[ 1 ];
21410
21411	return this.each( function() {
21412		var collisionPosition, using,
21413			elem = $( this ),
21414			elemWidth = elem.outerWidth(),
21415			elemHeight = elem.outerHeight(),
21416			marginLeft = parseCss( this, "marginLeft" ),
21417			marginTop = parseCss( this, "marginTop" ),
21418			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
21419				scrollInfo.width,
21420			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
21421				scrollInfo.height,
21422			position = $.extend( {}, basePosition ),
21423			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
21424
21425		if ( options.my[ 0 ] === "right" ) {
21426			position.left -= elemWidth;
21427		} else if ( options.my[ 0 ] === "center" ) {
21428			position.left -= elemWidth / 2;
21429		}
21430
21431		if ( options.my[ 1 ] === "bottom" ) {
21432			position.top -= elemHeight;
21433		} else if ( options.my[ 1 ] === "center" ) {
21434			position.top -= elemHeight / 2;
21435		}
21436
21437		position.left += myOffset[ 0 ];
21438		position.top += myOffset[ 1 ];
21439
21440		collisionPosition = {
21441			marginLeft: marginLeft,
21442			marginTop: marginTop
21443		};
21444
21445		$.each( [ "left", "top" ], function( i, dir ) {
21446			if ( $.ui.position[ collision[ i ] ] ) {
21447				$.ui.position[ collision[ i ] ][ dir ]( position, {
21448					targetWidth: targetWidth,
21449					targetHeight: targetHeight,
21450					elemWidth: elemWidth,
21451					elemHeight: elemHeight,
21452					collisionPosition: collisionPosition,
21453					collisionWidth: collisionWidth,
21454					collisionHeight: collisionHeight,
21455					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
21456					my: options.my,
21457					at: options.at,
21458					within: within,
21459					elem: elem
21460				} );
21461			}
21462		} );
21463
21464		if ( options.using ) {
21465
21466			// Adds feedback as second argument to using callback, if present
21467			using = function( props ) {
21468				var left = targetOffset.left - position.left,
21469					right = left + targetWidth - elemWidth,
21470					top = targetOffset.top - position.top,
21471					bottom = top + targetHeight - elemHeight,
21472					feedback = {
21473						target: {
21474							element: target,
21475							left: targetOffset.left,
21476							top: targetOffset.top,
21477							width: targetWidth,
21478							height: targetHeight
21479						},
21480						element: {
21481							element: elem,
21482							left: position.left,
21483							top: position.top,
21484							width: elemWidth,
21485							height: elemHeight
21486						},
21487						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
21488						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
21489					};
21490				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
21491					feedback.horizontal = "center";
21492				}
21493				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
21494					feedback.vertical = "middle";
21495				}
21496				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
21497					feedback.important = "horizontal";
21498				} else {
21499					feedback.important = "vertical";
21500				}
21501				options.using.call( this, props, feedback );
21502			};
21503		}
21504
21505		elem.offset( $.extend( position, { using: using } ) );
21506	} );
21507};
21508
21509$.ui.position = {
21510	fit: {
21511		left: function( position, data ) {
21512			var within = data.within,
21513				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
21514				outerWidth = within.width,
21515				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
21516				overLeft = withinOffset - collisionPosLeft,
21517				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
21518				newOverRight;
21519
21520			// Element is wider than within
21521			if ( data.collisionWidth > outerWidth ) {
21522
21523				// Element is initially over the left side of within
21524				if ( overLeft > 0 && overRight <= 0 ) {
21525					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
21526						withinOffset;
21527					position.left += overLeft - newOverRight;
21528
21529				// Element is initially over right side of within
21530				} else if ( overRight > 0 && overLeft <= 0 ) {
21531					position.left = withinOffset;
21532
21533				// Element is initially over both left and right sides of within
21534				} else {
21535					if ( overLeft > overRight ) {
21536						position.left = withinOffset + outerWidth - data.collisionWidth;
21537					} else {
21538						position.left = withinOffset;
21539					}
21540				}
21541
21542			// Too far left -> align with left edge
21543			} else if ( overLeft > 0 ) {
21544				position.left += overLeft;
21545
21546			// Too far right -> align with right edge
21547			} else if ( overRight > 0 ) {
21548				position.left -= overRight;
21549
21550			// Adjust based on position and margin
21551			} else {
21552				position.left = max( position.left - collisionPosLeft, position.left );
21553			}
21554		},
21555		top: function( position, data ) {
21556			var within = data.within,
21557				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
21558				outerHeight = data.within.height,
21559				collisionPosTop = position.top - data.collisionPosition.marginTop,
21560				overTop = withinOffset - collisionPosTop,
21561				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
21562				newOverBottom;
21563
21564			// Element is taller than within
21565			if ( data.collisionHeight > outerHeight ) {
21566
21567				// Element is initially over the top of within
21568				if ( overTop > 0 && overBottom <= 0 ) {
21569					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
21570						withinOffset;
21571					position.top += overTop - newOverBottom;
21572
21573				// Element is initially over bottom of within
21574				} else if ( overBottom > 0 && overTop <= 0 ) {
21575					position.top = withinOffset;
21576
21577				// Element is initially over both top and bottom of within
21578				} else {
21579					if ( overTop > overBottom ) {
21580						position.top = withinOffset + outerHeight - data.collisionHeight;
21581					} else {
21582						position.top = withinOffset;
21583					}
21584				}
21585
21586			// Too far up -> align with top
21587			} else if ( overTop > 0 ) {
21588				position.top += overTop;
21589
21590			// Too far down -> align with bottom edge
21591			} else if ( overBottom > 0 ) {
21592				position.top -= overBottom;
21593
21594			// Adjust based on position and margin
21595			} else {
21596				position.top = max( position.top - collisionPosTop, position.top );
21597			}
21598		}
21599	},
21600	flip: {
21601		left: function( position, data ) {
21602			var within = data.within,
21603				withinOffset = within.offset.left + within.scrollLeft,
21604				outerWidth = within.width,
21605				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
21606				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
21607				overLeft = collisionPosLeft - offsetLeft,
21608				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
21609				myOffset = data.my[ 0 ] === "left" ?
21610					-data.elemWidth :
21611					data.my[ 0 ] === "right" ?
21612						data.elemWidth :
21613						0,
21614				atOffset = data.at[ 0 ] === "left" ?
21615					data.targetWidth :
21616					data.at[ 0 ] === "right" ?
21617						-data.targetWidth :
21618						0,
21619				offset = -2 * data.offset[ 0 ],
21620				newOverRight,
21621				newOverLeft;
21622
21623			if ( overLeft < 0 ) {
21624				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
21625					outerWidth - withinOffset;
21626				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
21627					position.left += myOffset + atOffset + offset;
21628				}
21629			} else if ( overRight > 0 ) {
21630				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
21631					atOffset + offset - offsetLeft;
21632				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
21633					position.left += myOffset + atOffset + offset;
21634				}
21635			}
21636		},
21637		top: function( position, data ) {
21638			var within = data.within,
21639				withinOffset = within.offset.top + within.scrollTop,
21640				outerHeight = within.height,
21641				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
21642				collisionPosTop = position.top - data.collisionPosition.marginTop,
21643				overTop = collisionPosTop - offsetTop,
21644				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
21645				top = data.my[ 1 ] === "top",
21646				myOffset = top ?
21647					-data.elemHeight :
21648					data.my[ 1 ] === "bottom" ?
21649						data.elemHeight :
21650						0,
21651				atOffset = data.at[ 1 ] === "top" ?
21652					data.targetHeight :
21653					data.at[ 1 ] === "bottom" ?
21654						-data.targetHeight :
21655						0,
21656				offset = -2 * data.offset[ 1 ],
21657				newOverTop,
21658				newOverBottom;
21659			if ( overTop < 0 ) {
21660				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
21661					outerHeight - withinOffset;
21662				if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
21663					position.top += myOffset + atOffset + offset;
21664				}
21665			} else if ( overBottom > 0 ) {
21666				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
21667					offset - offsetTop;
21668				if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
21669					position.top += myOffset + atOffset + offset;
21670				}
21671			}
21672		}
21673	},
21674	flipfit: {
21675		left: function() {
21676			$.ui.position.flip.left.apply( this, arguments );
21677			$.ui.position.fit.left.apply( this, arguments );
21678		},
21679		top: function() {
21680			$.ui.position.flip.top.apply( this, arguments );
21681			$.ui.position.fit.top.apply( this, arguments );
21682		}
21683	}
21684};
21685
21686} )();
21687
21688return $.ui.position;
21689
21690} ) );
21691
21692
21693/*!
21694 * jQuery UI Widget 1.12.1
21695 * http://jqueryui.com
21696 *
21697 * Copyright jQuery Foundation and other contributors
21698 * Released under the MIT license.
21699 * http://jquery.org/license
21700 */
21701
21702//>>label: Widget
21703//>>group: Core
21704//>>description: Provides a factory for creating stateful widgets with a common API.
21705//>>docs: http://api.jqueryui.com/jQuery.widget/
21706//>>demos: http://jqueryui.com/widget/
21707
21708( function( factory ) {
21709	if ( typeof define === "function" && define.amd ) {
21710
21711		// AMD. Register as an anonymous module.
21712		define( [ "jquery", "./version" ], factory );
21713	} else {
21714
21715		// Browser globals
21716		factory( jQuery );
21717	}
21718}( function( $ ) {
vendor: 8,065 bytes, lines 21718-22010
21718
21719
21720var widgetUuid = 0;
21721var widgetSlice = Array.prototype.slice;
21722
21723$.cleanData = ( function( orig ) {
21724	return function( elems ) {
21725		var events, elem, i;
21726		for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
21727			try {
21728
21729				// Only trigger remove when necessary to save time
21730				events = $._data( elem, "events" );
21731				if ( events && events.remove ) {
21732					$( elem ).triggerHandler( "remove" );
21733				}
21734
21735			// Http://bugs.jquery.com/ticket/8235
21736			} catch ( e ) {}
21737		}
21738		orig( elems );
21739	};
21740} )( $.cleanData );
21741
21742$.widget = function( name, base, prototype ) {
21743	var existingConstructor, constructor, basePrototype;
21744
21745	// ProxiedPrototype allows the provided prototype to remain unmodified
21746	// so that it can be used as a mixin for multiple widgets (#8876)
21747	var proxiedPrototype = {};
21748
21749	var namespace = name.split( "." )[ 0 ];
21750	name = name.split( "." )[ 1 ];
21751	var fullName = namespace + "-" + name;
21752
21753	if ( !prototype ) {
21754		prototype = base;
21755		base = $.Widget;
21756	}
21757
21758	if ( $.isArray( prototype ) ) {
21759		prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
21760	}
21761
21762	// Create selector for plugin
21763	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
21764		return !!$.data( elem, fullName );
21765	};
21766
21767	$[ namespace ] = $[ namespace ] || {};
21768	existingConstructor = $[ namespace ][ name ];
21769	constructor = $[ namespace ][ name ] = function( options, element ) {
21770
21771		// Allow instantiation without "new" keyword
21772		if ( !this._createWidget ) {
21773			return new constructor( options, element );
21774		}
21775
21776		// Allow instantiation without initializing for simple inheritance
21777		// must use "new" keyword (the code above always passes args)
21778		if ( arguments.length ) {
21779			this._createWidget( options, element );
21780		}
21781	};
21782
21783	// Extend with the existing constructor to carry over any static properties
21784	$.extend( constructor, existingConstructor, {
21785		version: prototype.version,
21786
21787		// Copy the object used to create the prototype in case we need to
21788		// redefine the widget later
21789		_proto: $.extend( {}, prototype ),
21790
21791		// Track widgets that inherit from this widget in case this widget is
21792		// redefined after a widget inherits from it
21793		_childConstructors: []
21794	} );
21795
21796	basePrototype = new base();
21797
21798	// We need to make the options hash a property directly on the new instance
21799	// otherwise we'll modify the options hash on the prototype that we're
21800	// inheriting from
21801	basePrototype.options = $.widget.extend( {}, basePrototype.options );
21802	$.each( prototype, function( prop, value ) {
21803		if ( !$.isFunction( value ) ) {
21804			proxiedPrototype[ prop ] = value;
21805			return;
21806		}
21807		proxiedPrototype[ prop ] = ( function() {
21808			function _super() {
21809				return base.prototype[ prop ].apply( this, arguments );
21810			}
21811
21812			function _superApply( args ) {
21813				return base.prototype[ prop ].apply( this, args );
21814			}
21815
21816			return function() {
21817				var __super = this._super;
21818				var __superApply = this._superApply;
21819				var returnValue;
21820
21821				this._super = _super;
21822				this._superApply = _superApply;
21823
21824				returnValue = value.apply( this, arguments );
21825
21826				this._super = __super;
21827				this._superApply = __superApply;
21828
21829				return returnValue;
21830			};
21831		} )();
21832	} );
21833	constructor.prototype = $.widget.extend( basePrototype, {
21834
21835		// TODO: remove support for widgetEventPrefix
21836		// always use the name + a colon as the prefix, e.g., draggable:start
21837		// don't prefix for widgets that aren't DOM-based
21838		widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
21839	}, proxiedPrototype, {
21840		constructor: constructor,
21841		namespace: namespace,
21842		widgetName: name,
21843		widgetFullName: fullName
21844	} );
21845
21846	// If this widget is being redefined then we need to find all widgets that
21847	// are inheriting from it and redefine all of them so that they inherit from
21848	// the new version of this widget. We're essentially trying to replace one
21849	// level in the prototype chain.
21850	if ( existingConstructor ) {
21851		$.each( existingConstructor._childConstructors, function( i, child ) {
21852			var childPrototype = child.prototype;
21853
21854			// Redefine the child widget using the same prototype that was
21855			// originally used, but inherit from the new version of the base
21856			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
21857				child._proto );
21858		} );
21859
21860		// Remove the list of existing child constructors from the old constructor
21861		// so the old child constructors can be garbage collected
21862		delete existingConstructor._childConstructors;
21863	} else {
21864		base._childConstructors.push( constructor );
21865	}
21866
21867	$.widget.bridge( name, constructor );
21868
21869	return constructor;
21870};
21871
21872$.widget.extend = function( target ) {
21873	var input = widgetSlice.call( arguments, 1 );
21874	var inputIndex = 0;
21875	var inputLength = input.length;
21876	var key;
21877	var value;
21878
21879	for ( ; inputIndex < inputLength; inputIndex++ ) {
21880		for ( key in input[ inputIndex ] ) {
21881			value = input[ inputIndex ][ key ];
21882			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
21883
21884				// Clone objects
21885				if ( $.isPlainObject( value ) ) {
21886					target[ key ] = $.isPlainObject( target[ key ] ) ?
21887						$.widget.extend( {}, target[ key ], value ) :
21888
21889						// Don't extend strings, arrays, etc. with objects
21890						$.widget.extend( {}, value );
21891
21892				// Copy everything else by reference
21893				} else {
21894					target[ key ] = value;
21895				}
21896			}
21897		}
21898	}
21899	return target;
21900};
21901
21902$.widget.bridge = function( name, object ) {
21903	var fullName = object.prototype.widgetFullName || name;
21904	$.fn[ name ] = function( options ) {
21905		var isMethodCall = typeof options === "string";
21906		var args = widgetSlice.call( arguments, 1 );
21907		var returnValue = this;
21908
21909		if ( isMethodCall ) {
21910
21911			// If this is an empty collection, we need to have the instance method
21912			// return undefined instead of the jQuery instance
21913			if ( !this.length && options === "instance" ) {
21914				returnValue = undefined;
21915			} else {
21916				this.each( function() {
21917					var methodValue;
21918					var instance = $.data( this, fullName );
21919
21920					if ( options === "instance" ) {
21921						returnValue = instance;
21922						return false;
21923					}
21924
21925					if ( !instance ) {
21926						return $.error( "cannot call methods on " + name +
21927							" prior to initialization; " +
21928							"attempted to call method '" + options + "'" );
21929					}
21930
21931					if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) {
21932						return $.error( "no such method '" + options + "' for " + name +
21933							" widget instance" );
21934					}
21935
21936					methodValue = instance[ options ].apply( instance, args );
21937
21938					if ( methodValue !== instance && methodValue !== undefined ) {
21939						returnValue = methodValue && methodValue.jquery ?
21940							returnValue.pushStack( methodValue.get() ) :
21941							methodValue;
21942						return false;
21943					}
21944				} );
21945			}
21946		} else {
21947
21948			// Allow multiple hashes to be passed on init
21949			if ( args.length ) {
21950				options = $.widget.extend.apply( null, [ options ].concat( args ) );
21951			}
21952
21953			this.each( function() {
21954				var instance = $.data( this, fullName );
21955				if ( instance ) {
21956					instance.option( options || {} );
21957					if ( instance._init ) {
21958						instance._init();
21959					}
21960				} else {
21961					$.data( this, fullName, new object( options, this ) );
21962				}
21963			} );
21964		}
21965
21966		return returnValue;
21967	};
21968};
21969
21970$.Widget = function( /* options, element */ ) {};
21971$.Widget._childConstructors = [];
21972
21973$.Widget.prototype = {
21974	widgetName: "widget",
21975	widgetEventPrefix: "",
21976	defaultElement: "<div>",
21977
21978	options: {
21979		classes: {},
21980		disabled: false,
21981
21982		// Callbacks
21983		create: null
21984	},
21985
21986	_createWidget: function( options, element ) {
21987		element = $( element || this.defaultElement || this )[ 0 ];
21988		this.element = $( element );
21989		this.uuid = widgetUuid++;
21990		this.eventNamespace = "." + this.widgetName + this.uuid;
21991
21992		this.bindings = $();
21993		this.hoverable = $();
21994		this.focusable = $();
21995		this.classesElementLookup = {};
21996
21997		if ( element !== this ) {
21998			$.data( element, this.widgetFullName, this );
21999			this._on( true, this.element, {
22000				remove: function( event ) {
22001					if ( event.target === element ) {
22002						this.destroy();
22003					}
22004				}
22005			} );
22006			this.document = $( element.style ?
22007
22008				// Element within the document
22009				element.ownerDocument :
22010
vendor: 11,027 bytes, lines 22011-22422
22011				// Element is window or document
22012				element.document || element );
22013			this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
22014		}
22015
22016		this.options = $.widget.extend( {},
22017			this.options,
22018			this._getCreateOptions(),
22019			options );
22020
22021		this._create();
22022
22023		if ( this.options.disabled ) {
22024			this._setOptionDisabled( this.options.disabled );
22025		}
22026
22027		this._trigger( "create", null, this._getCreateEventData() );
22028		this._init();
22029	},
22030
22031	_getCreateOptions: function() {
22032		return {};
22033	},
22034
22035	_getCreateEventData: $.noop,
22036
22037	_create: $.noop,
22038
22039	_init: $.noop,
22040
22041	destroy: function() {
22042		var that = this;
22043
22044		this._destroy();
22045		$.each( this.classesElementLookup, function( key, value ) {
22046			that._removeClass( value, key );
22047		} );
22048
22049		// We can probably remove the unbind calls in 2.0
22050		// all event bindings should go through this._on()
22051		this.element
22052			.off( this.eventNamespace )
22053			.removeData( this.widgetFullName );
22054		this.widget()
22055			.off( this.eventNamespace )
22056			.removeAttr( "aria-disabled" );
22057
22058		// Clean up events and states
22059		this.bindings.off( this.eventNamespace );
22060	},
22061
22062	_destroy: $.noop,
22063
22064	widget: function() {
22065		return this.element;
22066	},
22067
22068	option: function( key, value ) {
22069		var options = key;
22070		var parts;
22071		var curOption;
22072		var i;
22073
22074		if ( arguments.length === 0 ) {
22075
22076			// Don't return a reference to the internal hash
22077			return $.widget.extend( {}, this.options );
22078		}
22079
22080		if ( typeof key === "string" ) {
22081
22082			// Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
22083			options = {};
22084			parts = key.split( "." );
22085			key = parts.shift();
22086			if ( parts.length ) {
22087				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
22088				for ( i = 0; i < parts.length - 1; i++ ) {
22089					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
22090					curOption = curOption[ parts[ i ] ];
22091				}
22092				key = parts.pop();
22093				if ( arguments.length === 1 ) {
22094					return curOption[ key ] === undefined ? null : curOption[ key ];
22095				}
22096				curOption[ key ] = value;
22097			} else {
22098				if ( arguments.length === 1 ) {
22099					return this.options[ key ] === undefined ? null : this.options[ key ];
22100				}
22101				options[ key ] = value;
22102			}
22103		}
22104
22105		this._setOptions( options );
22106
22107		return this;
22108	},
22109
22110	_setOptions: function( options ) {
22111		var key;
22112
22113		for ( key in options ) {
22114			this._setOption( key, options[ key ] );
22115		}
22116
22117		return this;
22118	},
22119
22120	_setOption: function( key, value ) {
22121		if ( key === "classes" ) {
22122			this._setOptionClasses( value );
22123		}
22124
22125		this.options[ key ] = value;
22126
22127		if ( key === "disabled" ) {
22128			this._setOptionDisabled( value );
22129		}
22130
22131		return this;
22132	},
22133
22134	_setOptionClasses: function( value ) {
22135		var classKey, elements, currentElements;
22136
22137		for ( classKey in value ) {
22138			currentElements = this.classesElementLookup[ classKey ];
22139			if ( value[ classKey ] === this.options.classes[ classKey ] ||
22140					!currentElements ||
22141					!currentElements.length ) {
22142				continue;
22143			}
22144
22145			// We are doing this to create a new jQuery object because the _removeClass() call
22146			// on the next line is going to destroy the reference to the current elements being
22147			// tracked. We need to save a copy of this collection so that we can add the new classes
22148			// below.
22149			elements = $( currentElements.get() );
22150			this._removeClass( currentElements, classKey );
22151
22152			// We don't use _addClass() here, because that uses this.options.classes
22153			// for generating the string of classes. We want to use the value passed in from
22154			// _setOption(), this is the new value of the classes option which was passed to
22155			// _setOption(). We pass this value directly to _classes().
22156			elements.addClass( this._classes( {
22157				element: elements,
22158				keys: classKey,
22159				classes: value,
22160				add: true
22161			} ) );
22162		}
22163	},
22164
22165	_setOptionDisabled: function( value ) {
22166		this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
22167
22168		// If the widget is becoming disabled, then nothing is interactive
22169		if ( value ) {
22170			this._removeClass( this.hoverable, null, "ui-state-hover" );
22171			this._removeClass( this.focusable, null, "ui-state-focus" );
22172		}
22173	},
22174
22175	enable: function() {
22176		return this._setOptions( { disabled: false } );
22177	},
22178
22179	disable: function() {
22180		return this._setOptions( { disabled: true } );
22181	},
22182
22183	_classes: function( options ) {
22184		var full = [];
22185		var that = this;
22186
22187		options = $.extend( {
22188			element: this.element,
22189			classes: this.options.classes || {}
22190		}, options );
22191
22192		function processClassString( classes, checkOption ) {
22193			var current, i;
22194			for ( i = 0; i < classes.length; i++ ) {
22195				current = that.classesElementLookup[ classes[ i ] ] || $();
22196				if ( options.add ) {
22197					current = $( $.unique( current.get().concat( options.element.get() ) ) );
22198				} else {
22199					current = $( current.not( options.element ).get() );
22200				}
22201				that.classesElementLookup[ classes[ i ] ] = current;
22202				full.push( classes[ i ] );
22203				if ( checkOption && options.classes[ classes[ i ] ] ) {
22204					full.push( options.classes[ classes[ i ] ] );
22205				}
22206			}
22207		}
22208
22209		this._on( options.element, {
22210			"remove": "_untrackClassesElement"
22211		} );
22212
22213		if ( options.keys ) {
22214			processClassString( options.keys.match( /\S+/g ) || [], true );
22215		}
22216		if ( options.extra ) {
22217			processClassString( options.extra.match( /\S+/g ) || [] );
22218		}
22219
22220		return full.join( " " );
22221	},
22222
22223	_untrackClassesElement: function( event ) {
22224		var that = this;
22225		$.each( that.classesElementLookup, function( key, value ) {
22226			if ( $.inArray( event.target, value ) !== -1 ) {
22227				that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
22228			}
22229		} );
22230	},
22231
22232	_removeClass: function( element, keys, extra ) {
22233		return this._toggleClass( element, keys, extra, false );
22234	},
22235
22236	_addClass: function( element, keys, extra ) {
22237		return this._toggleClass( element, keys, extra, true );
22238	},
22239
22240	_toggleClass: function( element, keys, extra, add ) {
22241		add = ( typeof add === "boolean" ) ? add : extra;
22242		var shift = ( typeof element === "string" || element === null ),
22243			options = {
22244				extra: shift ? keys : extra,
22245				keys: shift ? element : keys,
22246				element: shift ? this.element : element,
22247				add: add
22248			};
22249		options.element.toggleClass( this._classes( options ), add );
22250		return this;
22251	},
22252
22253	_on: function( suppressDisabledCheck, element, handlers ) {
22254		var delegateElement;
22255		var instance = this;
22256
22257		// No suppressDisabledCheck flag, shuffle arguments
22258		if ( typeof suppressDisabledCheck !== "boolean" ) {
22259			handlers = element;
22260			element = suppressDisabledCheck;
22261			suppressDisabledCheck = false;
22262		}
22263
22264		// No element argument, shuffle and use this.element
22265		if ( !handlers ) {
22266			handlers = element;
22267			element = this.element;
22268			delegateElement = this.widget();
22269		} else {
22270			element = delegateElement = $( element );
22271			this.bindings = this.bindings.add( element );
22272		}
22273
22274		$.each( handlers, function( event, handler ) {
22275			function handlerProxy() {
22276
22277				// Allow widgets to customize the disabled handling
22278				// - disabled as an array instead of boolean
22279				// - disabled class as method for disabling individual parts
22280				if ( !suppressDisabledCheck &&
22281						( instance.options.disabled === true ||
22282						$( this ).hasClass( "ui-state-disabled" ) ) ) {
22283					return;
22284				}
22285				return ( typeof handler === "string" ? instance[ handler ] : handler )
22286					.apply( instance, arguments );
22287			}
22288
22289			// Copy the guid so direct unbinding works
22290			if ( typeof handler !== "string" ) {
22291				handlerProxy.guid = handler.guid =
22292					handler.guid || handlerProxy.guid || $.guid++;
22293			}
22294
22295			var match = event.match( /^([\w:-]*)\s*(.*)$/ );
22296			var eventName = match[ 1 ] + instance.eventNamespace;
22297			var selector = match[ 2 ];
22298
22299			if ( selector ) {
22300				delegateElement.on( eventName, selector, handlerProxy );
22301			} else {
22302				element.on( eventName, handlerProxy );
22303			}
22304		} );
22305	},
22306
22307	_off: function( element, eventName ) {
22308		eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
22309			this.eventNamespace;
22310		element.off( eventName ).off( eventName );
22311
22312		// Clear the stack to avoid memory leaks (#10056)
22313		this.bindings = $( this.bindings.not( element ).get() );
22314		this.focusable = $( this.focusable.not( element ).get() );
22315		this.hoverable = $( this.hoverable.not( element ).get() );
22316	},
22317
22318	_delay: function( handler, delay ) {
22319		function handlerProxy() {
22320			return ( typeof handler === "string" ? instance[ handler ] : handler )
22321				.apply( instance, arguments );
22322		}
22323		var instance = this;
22324		return setTimeout( handlerProxy, delay || 0 );
22325	},
22326
22327	_hoverable: function( element ) {
22328		this.hoverable = this.hoverable.add( element );
22329		this._on( element, {
22330			mouseenter: function( event ) {
22331				this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
22332			},
22333			mouseleave: function( event ) {
22334				this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
22335			}
22336		} );
22337	},
22338
22339	_focusable: function( element ) {
22340		this.focusable = this.focusable.add( element );
22341		this._on( element, {
22342			focusin: function( event ) {
22343				this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
22344			},
22345			focusout: function( event ) {
22346				this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
22347			}
22348		} );
22349	},
22350
22351	_trigger: function( type, event, data ) {
22352		var prop, orig;
22353		var callback = this.options[ type ];
22354
22355		data = data || {};
22356		event = $.Event( event );
22357		event.type = ( type === this.widgetEventPrefix ?
22358			type :
22359			this.widgetEventPrefix + type ).toLowerCase();
22360
22361		// The original event may come from any element
22362		// so we need to reset the target on the new event
22363		event.target = this.element[ 0 ];
22364
22365		// Copy original event properties over to the new event
22366		orig = event.originalEvent;
22367		if ( orig ) {
22368			for ( prop in orig ) {
22369				if ( !( prop in event ) ) {
22370					event[ prop ] = orig[ prop ];
22371				}
22372			}
22373		}
22374
22375		this.element.trigger( event, data );
22376		return !( $.isFunction( callback ) &&
22377			callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
22378			event.isDefaultPrevented() );
22379	}
22380};
22381
22382$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
22383	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
22384		if ( typeof options === "string" ) {
22385			options = { effect: options };
22386		}
22387
22388		var hasOptions;
22389		var effectName = !options ?
22390			method :
22391			options === true || typeof options === "number" ?
22392				defaultEffect :
22393				options.effect || defaultEffect;
22394
22395		options = options || {};
22396		if ( typeof options === "number" ) {
22397			options = { duration: options };
22398		}
22399
22400		hasOptions = !$.isEmptyObject( options );
22401		options.complete = callback;
22402
22403		if ( options.delay ) {
22404			element.delay( options.delay );
22405		}
22406
22407		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
22408			element[ method ]( options );
22409		} else if ( effectName !== method && element[ effectName ] ) {
22410			element[ effectName ]( options.duration, options.easing, callback );
22411		} else {
22412			element.queue( function( next ) {
22413				$( this )[ method ]();
22414				if ( callback ) {
22415					callback.call( element[ 0 ] );
22416				}
22417				next();
22418			} );
22419		}
22420	};
22421} );
22422
vendor: 27,516 bytes, lines 22423-23585
22423return $.widget;
22424
22425} ) );
22426
22427
22428
22429/*!
22430 * jQuery UI Effects Blind 1.12.1
22431 * http://jqueryui.com
22432 *
22433 * Copyright jQuery Foundation and other contributors
22434 * Released under the MIT license.
22435 * http://jquery.org/license
22436 */
22437
22438//>>label: Blind Effect
22439//>>group: Effects
22440//>>description: Blinds the element.
22441//>>docs: http://api.jqueryui.com/blind-effect/
22442//>>demos: http://jqueryui.com/effect/
22443
22444( function( factory ) {
22445	if ( typeof define === "function" && define.amd ) {
22446
22447		// AMD. Register as an anonymous module.
22448		define( [
22449			"jquery",
22450			"../version",
22451			"../effect"
22452		], factory );
22453	} else {
22454
22455		// Browser globals
22456		factory( jQuery );
22457	}
22458}( function( $ ) {
22459
22460return $.effects.define( "blind", "hide", function( options, done ) {
22461	var map = {
22462			up: [ "bottom", "top" ],
22463			vertical: [ "bottom", "top" ],
22464			down: [ "top", "bottom" ],
22465			left: [ "right", "left" ],
22466			horizontal: [ "right", "left" ],
22467			right: [ "left", "right" ]
22468		},
22469		element = $( this ),
22470		direction = options.direction || "up",
22471		start = element.cssClip(),
22472		animate = { clip: $.extend( {}, start ) },
22473		placeholder = $.effects.createPlaceholder( element );
22474
22475	animate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ];
22476
22477	if ( options.mode === "show" ) {
22478		element.cssClip( animate.clip );
22479		if ( placeholder ) {
22480			placeholder.css( $.effects.clipToBox( animate ) );
22481		}
22482
22483		animate.clip = start;
22484	}
22485
22486	if ( placeholder ) {
22487		placeholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing );
22488	}
22489
22490	element.animate( animate, {
22491		queue: false,
22492		duration: options.duration,
22493		easing: options.easing,
22494		complete: done
22495	} );
22496} );
22497
22498} ) );
22499
22500
22501
22502/*!
22503 * jQuery UI Effects Bounce 1.12.1
22504 * http://jqueryui.com
22505 *
22506 * Copyright jQuery Foundation and other contributors
22507 * Released under the MIT license.
22508 * http://jquery.org/license
22509 */
22510
22511//>>label: Bounce Effect
22512//>>group: Effects
22513//>>description: Bounces an element horizontally or vertically n times.
22514//>>docs: http://api.jqueryui.com/bounce-effect/
22515//>>demos: http://jqueryui.com/effect/
22516
22517( function( factory ) {
22518	if ( typeof define === "function" && define.amd ) {
22519
22520		// AMD. Register as an anonymous module.
22521		define( [
22522			"jquery",
22523			"../version",
22524			"../effect"
22525		], factory );
22526	} else {
22527
22528		// Browser globals
22529		factory( jQuery );
22530	}
22531}( function( $ ) {
22532
22533return $.effects.define( "bounce", function( options, done ) {
22534	var upAnim, downAnim, refValue,
22535		element = $( this ),
22536
22537		// Defaults:
22538		mode = options.mode,
22539		hide = mode === "hide",
22540		show = mode === "show",
22541		direction = options.direction || "up",
22542		distance = options.distance,
22543		times = options.times || 5,
22544
22545		// Number of internal animations
22546		anims = times * 2 + ( show || hide ? 1 : 0 ),
22547		speed = options.duration / anims,
22548		easing = options.easing,
22549
22550		// Utility:
22551		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
22552		motion = ( direction === "up" || direction === "left" ),
22553		i = 0,
22554
22555		queuelen = element.queue().length;
22556
22557	$.effects.createPlaceholder( element );
22558
22559	refValue = element.css( ref );
22560
22561	// Default distance for the BIGGEST bounce is the outer Distance / 3
22562	if ( !distance ) {
22563		distance = element[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
22564	}
22565
22566	if ( show ) {
22567		downAnim = { opacity: 1 };
22568		downAnim[ ref ] = refValue;
22569
22570		// If we are showing, force opacity 0 and set the initial position
22571		// then do the "first" animation
22572		element
22573			.css( "opacity", 0 )
22574			.css( ref, motion ? -distance * 2 : distance * 2 )
22575			.animate( downAnim, speed, easing );
22576	}
22577
22578	// Start at the smallest distance if we are hiding
22579	if ( hide ) {
22580		distance = distance / Math.pow( 2, times - 1 );
22581	}
22582
22583	downAnim = {};
22584	downAnim[ ref ] = refValue;
22585
22586	// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
22587	for ( ; i < times; i++ ) {
22588		upAnim = {};
22589		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
22590
22591		element
22592			.animate( upAnim, speed, easing )
22593			.animate( downAnim, speed, easing );
22594
22595		distance = hide ? distance * 2 : distance / 2;
22596	}
22597
22598	// Last Bounce when Hiding
22599	if ( hide ) {
22600		upAnim = { opacity: 0 };
22601		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
22602
22603		element.animate( upAnim, speed, easing );
22604	}
22605
22606	element.queue( done );
22607
22608	$.effects.unshift( element, queuelen, anims + 1 );
22609} );
22610
22611} ) );
22612
22613
22614
22615/*!
22616 * jQuery UI Effects Clip 1.12.1
22617 * http://jqueryui.com
22618 *
22619 * Copyright jQuery Foundation and other contributors
22620 * Released under the MIT license.
22621 * http://jquery.org/license
22622 */
22623
22624//>>label: Clip Effect
22625//>>group: Effects
22626//>>description: Clips the element on and off like an old TV.
22627//>>docs: http://api.jqueryui.com/clip-effect/
22628//>>demos: http://jqueryui.com/effect/
22629
22630( function( factory ) {
22631	if ( typeof define === "function" && define.amd ) {
22632
22633		// AMD. Register as an anonymous module.
22634		define( [
22635			"jquery",
22636			"../version",
22637			"../effect"
22638		], factory );
22639	} else {
22640
22641		// Browser globals
22642		factory( jQuery );
22643	}
22644}( function( $ ) {
22645
22646return $.effects.define( "clip", "hide", function( options, done ) {
22647	var start,
22648		animate = {},
22649		element = $( this ),
22650		direction = options.direction || "vertical",
22651		both = direction === "both",
22652		horizontal = both || direction === "horizontal",
22653		vertical = both || direction === "vertical";
22654
22655	start = element.cssClip();
22656	animate.clip = {
22657		top: vertical ? ( start.bottom - start.top ) / 2 : start.top,
22658		right: horizontal ? ( start.right - start.left ) / 2 : start.right,
22659		bottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom,
22660		left: horizontal ? ( start.right - start.left ) / 2 : start.left
22661	};
22662
22663	$.effects.createPlaceholder( element );
22664
22665	if ( options.mode === "show" ) {
22666		element.cssClip( animate.clip );
22667		animate.clip = start;
22668	}
22669
22670	element.animate( animate, {
22671		queue: false,
22672		duration: options.duration,
22673		easing: options.easing,
22674		complete: done
22675	} );
22676
22677} );
22678
22679} ) );
22680
22681
22682
22683/*!
22684 * jQuery UI Effects Drop 1.12.1
22685 * http://jqueryui.com
22686 *
22687 * Copyright jQuery Foundation and other contributors
22688 * Released under the MIT license.
22689 * http://jquery.org/license
22690 */
22691
22692//>>label: Drop Effect
22693//>>group: Effects
22694//>>description: Moves an element in one direction and hides it at the same time.
22695//>>docs: http://api.jqueryui.com/drop-effect/
22696//>>demos: http://jqueryui.com/effect/
22697
22698( function( factory ) {
22699	if ( typeof define === "function" && define.amd ) {
22700
22701		// AMD. Register as an anonymous module.
22702		define( [
22703			"jquery",
22704			"../version",
22705			"../effect"
22706		], factory );
22707	} else {
22708
22709		// Browser globals
22710		factory( jQuery );
22711	}
22712}( function( $ ) {
22713
22714return $.effects.define( "drop", "hide", function( options, done ) {
22715
22716	var distance,
22717		element = $( this ),
22718		mode = options.mode,
22719		show = mode === "show",
22720		direction = options.direction || "left",
22721		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
22722		motion = ( direction === "up" || direction === "left" ) ? "-=" : "+=",
22723		oppositeMotion = ( motion === "+=" ) ? "-=" : "+=",
22724		animation = {
22725			opacity: 0
22726		};
22727
22728	$.effects.createPlaceholder( element );
22729
22730	distance = options.distance ||
22731		element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2;
22732
22733	animation[ ref ] = motion + distance;
22734
22735	if ( show ) {
22736		element.css( animation );
22737
22738		animation[ ref ] = oppositeMotion + distance;
22739		animation.opacity = 1;
22740	}
22741
22742	// Animate
22743	element.animate( animation, {
22744		queue: false,
22745		duration: options.duration,
22746		easing: options.easing,
22747		complete: done
22748	} );
22749} );
22750
22751} ) );
22752
22753
22754
22755/*!
22756 * jQuery UI Effects Explode 1.12.1
22757 * http://jqueryui.com
22758 *
22759 * Copyright jQuery Foundation and other contributors
22760 * Released under the MIT license.
22761 * http://jquery.org/license
22762 */
22763
22764//>>label: Explode Effect
22765//>>group: Effects
22766// jscs:disable maximumLineLength
22767//>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness.
22768// jscs:enable maximumLineLength
22769//>>docs: http://api.jqueryui.com/explode-effect/
22770//>>demos: http://jqueryui.com/effect/
22771
22772( function( factory ) {
22773	if ( typeof define === "function" && define.amd ) {
22774
22775		// AMD. Register as an anonymous module.
22776		define( [
22777			"jquery",
22778			"../version",
22779			"../effect"
22780		], factory );
22781	} else {
22782
22783		// Browser globals
22784		factory( jQuery );
22785	}
22786}( function( $ ) {
22787
22788return $.effects.define( "explode", "hide", function( options, done ) {
22789
22790	var i, j, left, top, mx, my,
22791		rows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3,
22792		cells = rows,
22793		element = $( this ),
22794		mode = options.mode,
22795		show = mode === "show",
22796
22797		// Show and then visibility:hidden the element before calculating offset
22798		offset = element.show().css( "visibility", "hidden" ).offset(),
22799
22800		// Width and height of a piece
22801		width = Math.ceil( element.outerWidth() / cells ),
22802		height = Math.ceil( element.outerHeight() / rows ),
22803		pieces = [];
22804
22805	// Children animate complete:
22806	function childComplete() {
22807		pieces.push( this );
22808		if ( pieces.length === rows * cells ) {
22809			animComplete();
22810		}
22811	}
22812
22813	// Clone the element for each row and cell.
22814	for ( i = 0; i < rows; i++ ) { // ===>
22815		top = offset.top + i * height;
22816		my = i - ( rows - 1 ) / 2;
22817
22818		for ( j = 0; j < cells; j++ ) { // |||
22819			left = offset.left + j * width;
22820			mx = j - ( cells - 1 ) / 2;
22821
22822			// Create a clone of the now hidden main element that will be absolute positioned
22823			// within a wrapper div off the -left and -top equal to size of our pieces
22824			element
22825				.clone()
22826				.appendTo( "body" )
22827				.wrap( "<div></div>" )
22828				.css( {
22829					position: "absolute",
22830					visibility: "visible",
22831					left: -j * width,
22832					top: -i * height
22833				} )
22834
22835				// Select the wrapper - make it overflow: hidden and absolute positioned based on
22836				// where the original was located +left and +top equal to the size of pieces
22837				.parent()
22838					.addClass( "ui-effects-explode" )
22839					.css( {
22840						position: "absolute",
22841						overflow: "hidden",
22842						width: width,
22843						height: height,
22844						left: left + ( show ? mx * width : 0 ),
22845						top: top + ( show ? my * height : 0 ),
22846						opacity: show ? 0 : 1
22847					} )
22848					.animate( {
22849						left: left + ( show ? 0 : mx * width ),
22850						top: top + ( show ? 0 : my * height ),
22851						opacity: show ? 1 : 0
22852					}, options.duration || 500, options.easing, childComplete );
22853		}
22854	}
22855
22856	function animComplete() {
22857		element.css( {
22858			visibility: "visible"
22859		} );
22860		$( pieces ).remove();
22861		done();
22862	}
22863} );
22864
22865} ) );
22866
22867
22868
22869/*!
22870 * jQuery UI Effects Fade 1.12.1
22871 * http://jqueryui.com
22872 *
22873 * Copyright jQuery Foundation and other contributors
22874 * Released under the MIT license.
22875 * http://jquery.org/license
22876 */
22877
22878//>>label: Fade Effect
22879//>>group: Effects
22880//>>description: Fades the element.
22881//>>docs: http://api.jqueryui.com/fade-effect/
22882//>>demos: http://jqueryui.com/effect/
22883
22884( function( factory ) {
22885	if ( typeof define === "function" && define.amd ) {
22886
22887		// AMD. Register as an anonymous module.
22888		define( [
22889			"jquery",
22890			"../version",
22891			"../effect"
22892		], factory );
22893	} else {
22894
22895		// Browser globals
22896		factory( jQuery );
22897	}
22898}( function( $ ) {
22899
22900return $.effects.define( "fade", "toggle", function( options, done ) {
22901	var show = options.mode === "show";
22902
22903	$( this )
22904		.css( "opacity", show ? 0 : 1 )
22905		.animate( {
22906			opacity: show ? 1 : 0
22907		}, {
22908			queue: false,
22909			duration: options.duration,
22910			easing: options.easing,
22911			complete: done
22912		} );
22913} );
22914
22915} ) );
22916
22917
22918
22919/*!
22920 * jQuery UI Effects Fold 1.12.1
22921 * http://jqueryui.com
22922 *
22923 * Copyright jQuery Foundation and other contributors
22924 * Released under the MIT license.
22925 * http://jquery.org/license
22926 */
22927
22928//>>label: Fold Effect
22929//>>group: Effects
22930//>>description: Folds an element first horizontally and then vertically.
22931//>>docs: http://api.jqueryui.com/fold-effect/
22932//>>demos: http://jqueryui.com/effect/
22933
22934( function( factory ) {
22935	if ( typeof define === "function" && define.amd ) {
22936
22937		// AMD. Register as an anonymous module.
22938		define( [
22939			"jquery",
22940			"../version",
22941			"../effect"
22942		], factory );
22943	} else {
22944
22945		// Browser globals
22946		factory( jQuery );
22947	}
22948}( function( $ ) {
22949
22950return $.effects.define( "fold", "hide", function( options, done ) {
22951
22952	// Create element
22953	var element = $( this ),
22954		mode = options.mode,
22955		show = mode === "show",
22956		hide = mode === "hide",
22957		size = options.size || 15,
22958		percent = /([0-9]+)%/.exec( size ),
22959		horizFirst = !!options.horizFirst,
22960		ref = horizFirst ? [ "right", "bottom" ] : [ "bottom", "right" ],
22961		duration = options.duration / 2,
22962
22963		placeholder = $.effects.createPlaceholder( element ),
22964
22965		start = element.cssClip(),
22966		animation1 = { clip: $.extend( {}, start ) },
22967		animation2 = { clip: $.extend( {}, start ) },
22968
22969		distance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ],
22970
22971		queuelen = element.queue().length;
22972
22973	if ( percent ) {
22974		size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
22975	}
22976	animation1.clip[ ref[ 0 ] ] = size;
22977	animation2.clip[ ref[ 0 ] ] = size;
22978	animation2.clip[ ref[ 1 ] ] = 0;
22979
22980	if ( show ) {
22981		element.cssClip( animation2.clip );
22982		if ( placeholder ) {
22983			placeholder.css( $.effects.clipToBox( animation2 ) );
22984		}
22985
22986		animation2.clip = start;
22987	}
22988
22989	// Animate
22990	element
22991		.queue( function( next ) {
22992			if ( placeholder ) {
22993				placeholder
22994					.animate( $.effects.clipToBox( animation1 ), duration, options.easing )
22995					.animate( $.effects.clipToBox( animation2 ), duration, options.easing );
22996			}
22997
22998			next();
22999		} )
23000		.animate( animation1, duration, options.easing )
23001		.animate( animation2, duration, options.easing )
23002		.queue( done );
23003
23004	$.effects.unshift( element, queuelen, 4 );
23005} );
23006
23007} ) );
23008
23009
23010
23011/*!
23012 * jQuery UI Effects Highlight 1.12.1
23013 * http://jqueryui.com
23014 *
23015 * Copyright jQuery Foundation and other contributors
23016 * Released under the MIT license.
23017 * http://jquery.org/license
23018 */
23019
23020//>>label: Highlight Effect
23021//>>group: Effects
23022//>>description: Highlights the background of an element in a defined color for a custom duration.
23023//>>docs: http://api.jqueryui.com/highlight-effect/
23024//>>demos: http://jqueryui.com/effect/
23025
23026( function( factory ) {
23027	if ( typeof define === "function" && define.amd ) {
23028
23029		// AMD. Register as an anonymous module.
23030		define( [
23031			"jquery",
23032			"../version",
23033			"../effect"
23034		], factory );
23035	} else {
23036
23037		// Browser globals
23038		factory( jQuery );
23039	}
23040}( function( $ ) {
23041
23042return $.effects.define( "highlight", "show", function( options, done ) {
23043	var element = $( this ),
23044		animation = {
23045			backgroundColor: element.css( "backgroundColor" )
23046		};
23047
23048	if ( options.mode === "hide" ) {
23049		animation.opacity = 0;
23050	}
23051
23052	$.effects.saveStyle( element );
23053
23054	element
23055		.css( {
23056			backgroundImage: "none",
23057			backgroundColor: options.color || "#ffff99"
23058		} )
23059		.animate( animation, {
23060			queue: false,
23061			duration: options.duration,
23062			easing: options.easing,
23063			complete: done
23064		} );
23065} );
23066
23067} ) );
23068
23069
23070
23071/*!
23072 * jQuery UI Effects Size 1.12.1
23073 * http://jqueryui.com
23074 *
23075 * Copyright jQuery Foundation and other contributors
23076 * Released under the MIT license.
23077 * http://jquery.org/license
23078 */
23079
23080//>>label: Size Effect
23081//>>group: Effects
23082//>>description: Resize an element to a specified width and height.
23083//>>docs: http://api.jqueryui.com/size-effect/
23084//>>demos: http://jqueryui.com/effect/
23085
23086( function( factory ) {
23087	if ( typeof define === "function" && define.amd ) {
23088
23089		// AMD. Register as an anonymous module.
23090		define( [
23091			"jquery",
23092			"../version",
23093			"../effect"
23094		], factory );
23095	} else {
23096
23097		// Browser globals
23098		factory( jQuery );
23099	}
23100}( function( $ ) {
23101
23102return $.effects.define( "size", function( options, done ) {
23103
23104	// Create element
23105	var baseline, factor, temp,
23106		element = $( this ),
23107
23108		// Copy for children
23109		cProps = [ "fontSize" ],
23110		vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
23111		hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],
23112
23113		// Set options
23114		mode = options.mode,
23115		restore = mode !== "effect",
23116		scale = options.scale || "both",
23117		origin = options.origin || [ "middle", "center" ],
23118		position = element.css( "position" ),
23119		pos = element.position(),
23120		original = $.effects.scaledDimensions( element ),
23121		from = options.from || original,
23122		to = options.to || $.effects.scaledDimensions( element, 0 );
23123
23124	$.effects.createPlaceholder( element );
23125
23126	if ( mode === "show" ) {
23127		temp = from;
23128		from = to;
23129		to = temp;
23130	}
23131
23132	// Set scaling factor
23133	factor = {
23134		from: {
23135			y: from.height / original.height,
23136			x: from.width / original.width
23137		},
23138		to: {
23139			y: to.height / original.height,
23140			x: to.width / original.width
23141		}
23142	};
23143
23144	// Scale the css box
23145	if ( scale === "box" || scale === "both" ) {
23146
23147		// Vertical props scaling
23148		if ( factor.from.y !== factor.to.y ) {
23149			from = $.effects.setTransition( element, vProps, factor.from.y, from );
23150			to = $.effects.setTransition( element, vProps, factor.to.y, to );
23151		}
23152
23153		// Horizontal props scaling
23154		if ( factor.from.x !== factor.to.x ) {
23155			from = $.effects.setTransition( element, hProps, factor.from.x, from );
23156			to = $.effects.setTransition( element, hProps, factor.to.x, to );
23157		}
23158	}
23159
23160	// Scale the content
23161	if ( scale === "content" || scale === "both" ) {
23162
23163		// Vertical props scaling
23164		if ( factor.from.y !== factor.to.y ) {
23165			from = $.effects.setTransition( element, cProps, factor.from.y, from );
23166			to = $.effects.setTransition( element, cProps, factor.to.y, to );
23167		}
23168	}
23169
23170	// Adjust the position properties based on the provided origin points
23171	if ( origin ) {
23172		baseline = $.effects.getBaseline( origin, original );
23173		from.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top;
23174		from.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left;
23175		to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top;
23176		to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left;
23177	}
23178	element.css( from );
23179
23180	// Animate the children if desired
23181	if ( scale === "content" || scale === "both" ) {
23182
23183		vProps = vProps.concat( [ "marginTop", "marginBottom" ] ).concat( cProps );
23184		hProps = hProps.concat( [ "marginLeft", "marginRight" ] );
23185
23186		// Only animate children with width attributes specified
23187		// TODO: is this right? should we include anything with css width specified as well
23188		element.find( "*[width]" ).each( function() {
23189			var child = $( this ),
23190				childOriginal = $.effects.scaledDimensions( child ),
23191				childFrom = {
23192					height: childOriginal.height * factor.from.y,
23193					width: childOriginal.width * factor.from.x,
23194					outerHeight: childOriginal.outerHeight * factor.from.y,
23195					outerWidth: childOriginal.outerWidth * factor.from.x
23196				},
23197				childTo = {
23198					height: childOriginal.height * factor.to.y,
23199					width: childOriginal.width * factor.to.x,
23200					outerHeight: childOriginal.height * factor.to.y,
23201					outerWidth: childOriginal.width * factor.to.x
23202				};
23203
23204			// Vertical props scaling
23205			if ( factor.from.y !== factor.to.y ) {
23206				childFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom );
23207				childTo = $.effects.setTransition( child, vProps, factor.to.y, childTo );
23208			}
23209
23210			// Horizontal props scaling
23211			if ( factor.from.x !== factor.to.x ) {
23212				childFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom );
23213				childTo = $.effects.setTransition( child, hProps, factor.to.x, childTo );
23214			}
23215
23216			if ( restore ) {
23217				$.effects.saveStyle( child );
23218			}
23219
23220			// Animate children
23221			child.css( childFrom );
23222			child.animate( childTo, options.duration, options.easing, function() {
23223
23224				// Restore children
23225				if ( restore ) {
23226					$.effects.restoreStyle( child );
23227				}
23228			} );
23229		} );
23230	}
23231
23232	// Animate
23233	element.animate( to, {
23234		queue: false,
23235		duration: options.duration,
23236		easing: options.easing,
23237		complete: function() {
23238
23239			var offset = element.offset();
23240
23241			if ( to.opacity === 0 ) {
23242				element.css( "opacity", from.opacity );
23243			}
23244
23245			if ( !restore ) {
23246				element
23247					.css( "position", position === "static" ? "relative" : position )
23248					.offset( offset );
23249
23250				// Need to save style here so that automatic style restoration
23251				// doesn't restore to the original styles from before the animation.
23252				$.effects.saveStyle( element );
23253			}
23254
23255			done();
23256		}
23257	} );
23258
23259} );
23260
23261} ) );
23262
23263
23264
23265
23266/*!
23267 * jQuery UI Effects Scale 1.12.1
23268 * http://jqueryui.com
23269 *
23270 * Copyright jQuery Foundation and other contributors
23271 * Released under the MIT license.
23272 * http://jquery.org/license
23273 */
23274
23275//>>label: Scale Effect
23276//>>group: Effects
23277//>>description: Grows or shrinks an element and its content.
23278//>>docs: http://api.jqueryui.com/scale-effect/
23279//>>demos: http://jqueryui.com/effect/
23280
23281( function( factory ) {
23282	if ( typeof define === "function" && define.amd ) {
23283
23284		// AMD. Register as an anonymous module.
23285		define( [
23286			"jquery",
23287			"../version",
23288			"../effect",
23289			"./effect-size"
23290		], factory );
23291	} else {
23292
23293		// Browser globals
23294		factory( jQuery );
23295	}
23296}( function( $ ) {
23297
23298return $.effects.define( "scale", function( options, done ) {
23299
23300	// Create element
23301	var el = $( this ),
23302		mode = options.mode,
23303		percent = parseInt( options.percent, 10 ) ||
23304			( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== "effect" ? 0 : 100 ) ),
23305
23306		newOptions = $.extend( true, {
23307			from: $.effects.scaledDimensions( el ),
23308			to: $.effects.scaledDimensions( el, percent, options.direction || "both" ),
23309			origin: options.origin || [ "middle", "center" ]
23310		}, options );
23311
23312	// Fade option to support puff
23313	if ( options.fade ) {
23314		newOptions.from.opacity = 1;
23315		newOptions.to.opacity = 0;
23316	}
23317
23318	$.effects.effect.size.call( this, newOptions, done );
23319} );
23320
23321} ) );
23322
23323
23324
23325
23326/*!
23327 * jQuery UI Effects Puff 1.12.1
23328 * http://jqueryui.com
23329 *
23330 * Copyright jQuery Foundation and other contributors
23331 * Released under the MIT license.
23332 * http://jquery.org/license
23333 */
23334
23335//>>label: Puff Effect
23336//>>group: Effects
23337//>>description: Creates a puff effect by scaling the element up and hiding it at the same time.
23338//>>docs: http://api.jqueryui.com/puff-effect/
23339//>>demos: http://jqueryui.com/effect/
23340
23341( function( factory ) {
23342	if ( typeof define === "function" && define.amd ) {
23343
23344		// AMD. Register as an anonymous module.
23345		define( [
23346			"jquery",
23347			"../version",
23348			"../effect",
23349			"./effect-scale"
23350		], factory );
23351	} else {
23352
23353		// Browser globals
23354		factory( jQuery );
23355	}
23356}( function( $ ) {
23357
23358return $.effects.define( "puff", "hide", function( options, done ) {
23359	var newOptions = $.extend( true, {}, options, {
23360		fade: true,
23361		percent: parseInt( options.percent, 10 ) || 150
23362	} );
23363
23364	$.effects.effect.scale.call( this, newOptions, done );
23365} );
23366
23367} ) );
23368
23369
23370
23371/*!
23372 * jQuery UI Effects Pulsate 1.12.1
23373 * http://jqueryui.com
23374 *
23375 * Copyright jQuery Foundation and other contributors
23376 * Released under the MIT license.
23377 * http://jquery.org/license
23378 */
23379
23380//>>label: Pulsate Effect
23381//>>group: Effects
23382//>>description: Pulsates an element n times by changing the opacity to zero and back.
23383//>>docs: http://api.jqueryui.com/pulsate-effect/
23384//>>demos: http://jqueryui.com/effect/
23385
23386( function( factory ) {
23387	if ( typeof define === "function" && define.amd ) {
23388
23389		// AMD. Register as an anonymous module.
23390		define( [
23391			"jquery",
23392			"../version",
23393			"../effect"
23394		], factory );
23395	} else {
23396
23397		// Browser globals
23398		factory( jQuery );
23399	}
23400}( function( $ ) {
23401
23402return $.effects.define( "pulsate", "show", function( options, done ) {
23403	var element = $( this ),
23404		mode = options.mode,
23405		show = mode === "show",
23406		hide = mode === "hide",
23407		showhide = show || hide,
23408
23409		// Showing or hiding leaves off the "last" animation
23410		anims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
23411		duration = options.duration / anims,
23412		animateTo = 0,
23413		i = 1,
23414		queuelen = element.queue().length;
23415
23416	if ( show || !element.is( ":visible" ) ) {
23417		element.css( "opacity", 0 ).show();
23418		animateTo = 1;
23419	}
23420
23421	// Anims - 1 opacity "toggles"
23422	for ( ; i < anims; i++ ) {
23423		element.animate( { opacity: animateTo }, duration, options.easing );
23424		animateTo = 1 - animateTo;
23425	}
23426
23427	element.animate( { opacity: animateTo }, duration, options.easing );
23428
23429	element.queue( done );
23430
23431	$.effects.unshift( element, queuelen, anims + 1 );
23432} );
23433
23434} ) );
23435
23436
23437
23438/*!
23439 * jQuery UI Effects Shake 1.12.1
23440 * http://jqueryui.com
23441 *
23442 * Copyright jQuery Foundation and other contributors
23443 * Released under the MIT license.
23444 * http://jquery.org/license
23445 */
23446
23447//>>label: Shake Effect
23448//>>group: Effects
23449//>>description: Shakes an element horizontally or vertically n times.
23450//>>docs: http://api.jqueryui.com/shake-effect/
23451//>>demos: http://jqueryui.com/effect/
23452
23453( function( factory ) {
23454	if ( typeof define === "function" && define.amd ) {
23455
23456		// AMD. Register as an anonymous module.
23457		define( [
23458			"jquery",
23459			"../version",
23460			"../effect"
23461		], factory );
23462	} else {
23463
23464		// Browser globals
23465		factory( jQuery );
23466	}
23467}( function( $ ) {
23468
23469return $.effects.define( "shake", function( options, done ) {
23470
23471	var i = 1,
23472		element = $( this ),
23473		direction = options.direction || "left",
23474		distance = options.distance || 20,
23475		times = options.times || 3,
23476		anims = times * 2 + 1,
23477		speed = Math.round( options.duration / anims ),
23478		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
23479		positiveMotion = ( direction === "up" || direction === "left" ),
23480		animation = {},
23481		animation1 = {},
23482		animation2 = {},
23483
23484		queuelen = element.queue().length;
23485
23486	$.effects.createPlaceholder( element );
23487
23488	// Animation
23489	animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
23490	animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
23491	animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;
23492
23493	// Animate
23494	element.animate( animation, speed, options.easing );
23495
23496	// Shakes
23497	for ( ; i < times; i++ ) {
23498		element
23499			.animate( animation1, speed, options.easing )
23500			.animate( animation2, speed, options.easing );
23501	}
23502
23503	element
23504		.animate( animation1, speed, options.easing )
23505		.animate( animation, speed / 2, options.easing )
23506		.queue( done );
23507
23508	$.effects.unshift( element, queuelen, anims + 1 );
23509} );
23510
23511} ) );
23512
23513
23514
23515/*!
23516 * jQuery UI Effects Slide 1.12.1
23517 * http://jqueryui.com
23518 *
23519 * Copyright jQuery Foundation and other contributors
23520 * Released under the MIT license.
23521 * http://jquery.org/license
23522 */
23523
23524//>>label: Slide Effect
23525//>>group: Effects
23526//>>description: Slides an element in and out of the viewport.
23527//>>docs: http://api.jqueryui.com/slide-effect/
23528//>>demos: http://jqueryui.com/effect/
23529
23530( function( factory ) {
23531	if ( typeof define === "function" && define.amd ) {
23532
23533		// AMD. Register as an anonymous module.
23534		define( [
23535			"jquery",
23536			"../version",
23537			"../effect"
23538		], factory );
23539	} else {
23540
23541		// Browser globals
23542		factory( jQuery );
23543	}
23544}( function( $ ) {
23545
23546return $.effects.define( "slide", "show", function( options, done ) {
23547	var startClip, startRef,
23548		element = $( this ),
23549		map = {
23550			up: [ "bottom", "top" ],
23551			down: [ "top", "bottom" ],
23552			left: [ "right", "left" ],
23553			right: [ "left", "right" ]
23554		},
23555		mode = options.mode,
23556		direction = options.direction || "left",
23557		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
23558		positiveMotion = ( direction === "up" || direction === "left" ),
23559		distance = options.distance ||
23560			element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ),
23561		animation = {};
23562
23563	$.effects.createPlaceholder( element );
23564
23565	startClip = element.cssClip();
23566	startRef = element.position()[ ref ];
23567
23568	// Define hide animation
23569	animation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef;
23570	animation.clip = element.cssClip();
23571	animation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ];
23572
23573	// Reverse the animation if we're showing
23574	if ( mode === "show" ) {
23575		element.cssClip( animation.clip );
23576		element.css( ref, animation[ ref ] );
23577		animation.clip = startClip;
23578		animation[ ref ] = startRef;
23579	}
23580
23581	// Actually animate
23582	element.animate( animation, {
23583		queue: false,
23584		duration: options.duration,
23585		easing: options.easing,
23586		complete: done
23587	} );
23588} );
23589
23590} ) );
23591
23592
23593
23594/*!
23595 * jQuery UI Effects Transfer 1.12.1
23596 * http://jqueryui.com
23597 *
23598 * Copyright jQuery Foundation and other contributors
23599 * Released under the MIT license.
23600 * http://jquery.org/license
23601 */
23602
23603//>>label: Transfer Effect
23604//>>group: Effects
23605//>>description: Displays a transfer effect from one element to another.
23606//>>docs: http://api.jqueryui.com/transfer-effect/
23607//>>demos: http://jqueryui.com/effect/
23608
23609( function( factory ) {
23610	if ( typeof define === "function" && define.amd ) {
23611
23612		// AMD. Register as an anonymous module.
23613		define( [
23614			"jquery",
23615			"../version",
23616			"../effect"
23617		], factory );
23618	} else {
23619
23620		// Browser globals
23621		factory( jQuery );
23622	}
23623}( function( $ ) {
23624
23625var effect;
23626if ( $.uiBackCompat !== false ) {
23627	effect = $.effects.define( "transfer", function( options, done ) {
23628		$( this ).transfer( options, done );
23629	} );
23630}
23631return effect;
23632
23633} ) );
23634
23635
23636
23637
23638
23639/*!
23640 * jQuery UI Accordion 1.12.1
23641 * http://jqueryui.com
23642 *
23643 * Copyright jQuery Foundation and other contributors
23644 * Released under the MIT license.
23645 * http://jquery.org/license
23646 */
23647
23648//>>label: Accordion
23649//>>group: Widgets
23650// jscs:disable maximumLineLength
23651//>>description: Displays collapsible content panels for presenting information in a limited amount of space.
23652// jscs:enable maximumLineLength
23653//>>docs: http://api.jqueryui.com/accordion/
23654//>>demos: http://jqueryui.com/accordion/
23655//>>css.structure: ../../themes/base/core.css
23656//>>css.structure: ../../themes/base/accordion.css
23657//>>css.theme: ../../themes/base/theme.css
23658
23659( function( factory ) {
23660	if ( typeof define === "function" && define.amd ) {
23661
23662		// AMD. Register as an anonymous module.
23663		define( [
23664			"jquery",
23665			"../version",
23666			"../keycode",
23667			"../unique-id",
23668			"../widget"
23669		], factory );
23670	} else {
23671
23672		// Browser globals
23673		factory( jQuery );
23674	}
23675}( function( $ ) {
23676
23677return
vendor: 14,973 bytes, lines 23677-24250
23677 $.widget( "ui.accordion", {
23678	version: "1.12.1",
23679	options: {
23680		active: 0,
23681		animate: {},
23682		classes: {
23683			"ui-accordion-header": "ui-corner-top",
23684			"ui-accordion-header-collapsed": "ui-corner-all",
23685			"ui-accordion-content": "ui-corner-bottom"
23686		},
23687		collapsible: false,
23688		event: "click",
23689		header: "> li > :first-child, > :not(li):even",
23690		heightStyle: "auto",
23691		icons: {
23692			activeHeader: "ui-icon-triangle-1-s",
23693			header: "ui-icon-triangle-1-e"
23694		},
23695
23696		// Callbacks
23697		activate: null,
23698		beforeActivate: null
23699	},
23700
23701	hideProps: {
23702		borderTopWidth: "hide",
23703		borderBottomWidth: "hide",
23704		paddingTop: "hide",
23705		paddingBottom: "hide",
23706		height: "hide"
23707	},
23708
23709	showProps: {
23710		borderTopWidth: "show",
23711		borderBottomWidth: "show",
23712		paddingTop: "show",
23713		paddingBottom: "show",
23714		height: "show"
23715	},
23716
23717	_create: function() {
23718		var options = this.options;
23719
23720		this.prevShow = this.prevHide = $();
23721		this._addClass( "ui-accordion", "ui-widget ui-helper-reset" );
23722		this.element.attr( "role", "tablist" );
23723
23724		// Don't allow collapsible: false and active: false / null
23725		if ( !options.collapsible && ( options.active === false || options.active == null ) ) {
23726			options.active = 0;
23727		}
23728
23729		this._processPanels();
23730
23731		// handle negative values
23732		if ( options.active < 0 ) {
23733			options.active += this.headers.length;
23734		}
23735		this._refresh();
23736	},
23737
23738	_getCreateEventData: function() {
23739		return {
23740			header: this.active,
23741			panel: !this.active.length ? $() : this.active.next()
23742		};
23743	},
23744
23745	_createIcons: function() {
23746		var icon, children,
23747			icons = this.options.icons;
23748
23749		if ( icons ) {
23750			icon = $( "<span>" );
23751			this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header );
23752			icon.prependTo( this.headers );
23753			children = this.active.children( ".ui-accordion-header-icon" );
23754			this._removeClass( children, icons.header )
23755				._addClass( children, null, icons.activeHeader )
23756				._addClass( this.headers, "ui-accordion-icons" );
23757		}
23758	},
23759
23760	_destroyIcons: function() {
23761		this._removeClass( this.headers, "ui-accordion-icons" );
23762		this.headers.children( ".ui-accordion-header-icon" ).remove();
23763	},
23764
23765	_destroy: function() {
23766		var contents;
23767
23768		// Clean up main element
23769		this.element.removeAttr( "role" );
23770
23771		// Clean up headers
23772		this.headers
23773			.removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" )
23774			.removeUniqueId();
23775
23776		this._destroyIcons();
23777
23778		// Clean up content panels
23779		contents = this.headers.next()
23780			.css( "display", "" )
23781			.removeAttr( "role aria-hidden aria-labelledby" )
23782			.removeUniqueId();
23783
23784		if ( this.options.heightStyle !== "content" ) {
23785			contents.css( "height", "" );
23786		}
23787	},
23788
23789	_setOption: function( key, value ) {
23790		if ( key === "active" ) {
23791
23792			// _activate() will handle invalid values and update this.options
23793			this._activate( value );
23794			return;
23795		}
23796
23797		if ( key === "event" ) {
23798			if ( this.options.event ) {
23799				this._off( this.headers, this.options.event );
23800			}
23801			this._setupEvents( value );
23802		}
23803
23804		this._super( key, value );
23805
23806		// Setting collapsible: false while collapsed; open first panel
23807		if ( key === "collapsible" && !value && this.options.active === false ) {
23808			this._activate( 0 );
23809		}
23810
23811		if ( key === "icons" ) {
23812			this._destroyIcons();
23813			if ( value ) {
23814				this._createIcons();
23815			}
23816		}
23817	},
23818
23819	_setOptionDisabled: function( value ) {
23820		this._super( value );
23821
23822		this.element.attr( "aria-disabled", value );
23823
23824		// Support: IE8 Only
23825		// #5332 / #6059 - opacity doesn't cascade to positioned elements in IE
23826		// so we need to add the disabled class to the headers and panels
23827		this._toggleClass( null, "ui-state-disabled", !!value );
23828		this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled",
23829			!!value );
23830	},
23831
23832	_keydown: function( event ) {
23833		if ( event.altKey || event.ctrlKey ) {
23834			return;
23835		}
23836
23837		var keyCode = $.ui.keyCode,
23838			length = this.headers.length,
23839			currentIndex = this.headers.index( event.target ),
23840			toFocus = false;
23841
23842		switch ( event.keyCode ) {
23843		case keyCode.RIGHT:
23844		case keyCode.DOWN:
23845			toFocus = this.headers[ ( currentIndex + 1 ) % length ];
23846			break;
23847		case keyCode.LEFT:
23848		case keyCode.UP:
23849			toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
23850			break;
23851		case keyCode.SPACE:
23852		case keyCode.ENTER:
23853			this._eventHandler( event );
23854			break;
23855		case keyCode.HOME:
23856			toFocus = this.headers[ 0 ];
23857			break;
23858		case keyCode.END:
23859			toFocus = this.headers[ length - 1 ];
23860			break;
23861		}
23862
23863		if ( toFocus ) {
23864			$( event.target ).attr( "tabIndex", -1 );
23865			$( toFocus ).attr( "tabIndex", 0 );
23866			$( toFocus ).trigger( "focus" );
23867			event.preventDefault();
23868		}
23869	},
23870
23871	_panelKeyDown: function( event ) {
23872		if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
23873			$( event.currentTarget ).prev().trigger( "focus" );
23874		}
23875	},
23876
23877	refresh: function() {
23878		var options = this.options;
23879		this._processPanels();
23880
23881		// Was collapsed or no panel
23882		if ( ( options.active === false && options.collapsible === true ) ||
23883				!this.headers.length ) {
23884			options.active = false;
23885			this.active = $();
23886
23887		// active false only when collapsible is true
23888		} else if ( options.active === false ) {
23889			this._activate( 0 );
23890
23891		// was active, but active panel is gone
23892		} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
23893
23894			// all remaining panel are disabled
23895			if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) {
23896				options.active = false;
23897				this.active = $();
23898
23899			// activate previous panel
23900			} else {
23901				this._activate( Math.max( 0, options.active - 1 ) );
23902			}
23903
23904		// was active, active panel still exists
23905		} else {
23906
23907			// make sure active index is correct
23908			options.active = this.headers.index( this.active );
23909		}
23910
23911		this._destroyIcons();
23912
23913		this._refresh();
23914	},
23915
23916	_processPanels: function() {
23917		var prevHeaders = this.headers,
23918			prevPanels = this.panels;
23919
23920		this.headers = this.element.find( this.options.header );
23921		this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed",
23922			"ui-state-default" );
23923
23924		this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide();
23925		this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" );
23926
23927		// Avoid memory leaks (#10056)
23928		if ( prevPanels ) {
23929			this._off( prevHeaders.not( this.headers ) );
23930			this._off( prevPanels.not( this.panels ) );
23931		}
23932	},
23933
23934	_refresh: function() {
23935		var maxHeight,
23936			options = this.options,
23937			heightStyle = options.heightStyle,
23938			parent = this.element.parent();
23939
23940		this.active = this._findActive( options.active );
23941		this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" )
23942			._removeClass( this.active, "ui-accordion-header-collapsed" );
23943		this._addClass( this.active.next(), "ui-accordion-content-active" );
23944		this.active.next().show();
23945
23946		this.headers
23947			.attr( "role", "tab" )
23948			.each( function() {
23949				var header = $( this ),
23950					headerId = header.uniqueId().attr( "id" ),
23951					panel = header.next(),
23952					panelId = panel.uniqueId().attr( "id" );
23953				header.attr( "aria-controls", panelId );
23954				panel.attr( "aria-labelledby", headerId );
23955			} )
23956			.next()
23957				.attr( "role", "tabpanel" );
23958
23959		this.headers
23960			.not( this.active )
23961				.attr( {
23962					"aria-selected": "false",
23963					"aria-expanded": "false",
23964					tabIndex: -1
23965				} )
23966				.next()
23967					.attr( {
23968						"aria-hidden": "true"
23969					} )
23970					.hide();
23971
23972		// Make sure at least one header is in the tab order
23973		if ( !this.active.length ) {
23974			this.headers.eq( 0 ).attr( "tabIndex", 0 );
23975		} else {
23976			this.active.attr( {
23977				"aria-selected": "true",
23978				"aria-expanded": "true",
23979				tabIndex: 0
23980			} )
23981				.next()
23982					.attr( {
23983						"aria-hidden": "false"
23984					} );
23985		}
23986
23987		this._createIcons();
23988
23989		this._setupEvents( options.event );
23990
23991		if ( heightStyle === "fill" ) {
23992			maxHeight = parent.height();
23993			this.element.siblings( ":visible" ).each( function() {
23994				var elem = $( this ),
23995					position = elem.css( "position" );
23996
23997				if ( position === "absolute" || position === "fixed" ) {
23998					return;
23999				}
24000				maxHeight -= elem.outerHeight( true );
24001			} );
24002
24003			this.headers.each( function() {
24004				maxHeight -= $( this ).outerHeight( true );
24005			} );
24006
24007			this.headers.next()
24008				.each( function() {
24009					$( this ).height( Math.max( 0, maxHeight -
24010						$( this ).innerHeight() + $( this ).height() ) );
24011				} )
24012				.css( "overflow", "auto" );
24013		} else if ( heightStyle === "auto" ) {
24014			maxHeight = 0;
24015			this.headers.next()
24016				.each( function() {
24017					var isVisible = $( this ).is( ":visible" );
24018					if ( !isVisible ) {
24019						$( this ).show();
24020					}
24021					maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
24022					if ( !isVisible ) {
24023						$( this ).hide();
24024					}
24025				} )
24026				.height( maxHeight );
24027		}
24028	},
24029
24030	_activate: function( index ) {
24031		var active = this._findActive( index )[ 0 ];
24032
24033		// Trying to activate the already active panel
24034		if ( active === this.active[ 0 ] ) {
24035			return;
24036		}
24037
24038		// Trying to collapse, simulate a click on the currently active header
24039		active = active || this.active[ 0 ];
24040
24041		this._eventHandler( {
24042			target: active,
24043			currentTarget: active,
24044			preventDefault: $.noop
24045		} );
24046	},
24047
24048	_findActive: function( selector ) {
24049		return typeof selector === "number" ? this.headers.eq( selector ) : $();
24050	},
24051
24052	_setupEvents: function( event ) {
24053		var events = {
24054			keydown: "_keydown"
24055		};
24056		if ( event ) {
24057			$.each( event.split( " " ), function( index, eventName ) {
24058				events[ eventName ] = "_eventHandler";
24059			} );
24060		}
24061
24062		this._off( this.headers.add( this.headers.next() ) );
24063		this._on( this.headers, events );
24064		this._on( this.headers.next(), { keydown: "_panelKeyDown" } );
24065		this._hoverable( this.headers );
24066		this._focusable( this.headers );
24067	},
24068
24069	_eventHandler: function( event ) {
24070		var activeChildren, clickedChildren,
24071			options = this.options,
24072			active = this.active,
24073			clicked = $( event.currentTarget ),
24074			clickedIsActive = clicked[ 0 ] === active[ 0 ],
24075			collapsing = clickedIsActive && options.collapsible,
24076			toShow = collapsing ? $() : clicked.next(),
24077			toHide = active.next(),
24078			eventData = {
24079				oldHeader: active,
24080				oldPanel: toHide,
24081				newHeader: collapsing ? $() : clicked,
24082				newPanel: toShow
24083			};
24084
24085		event.preventDefault();
24086
24087		if (
24088
24089				// click on active header, but not collapsible
24090				( clickedIsActive && !options.collapsible ) ||
24091
24092				// allow canceling activation
24093				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
24094			return;
24095		}
24096
24097		options.active = collapsing ? false : this.headers.index( clicked );
24098
24099		// When the call to ._toggle() comes after the class changes
24100		// it causes a very odd bug in IE 8 (see #6720)
24101		this.active = clickedIsActive ? $() : clicked;
24102		this._toggle( eventData );
24103
24104		// Switch classes
24105		// corner classes on the previously active header stay after the animation
24106		this._removeClass( active, "ui-accordion-header-active", "ui-state-active" );
24107		if ( options.icons ) {
24108			activeChildren = active.children( ".ui-accordion-header-icon" );
24109			this._removeClass( activeChildren, null, options.icons.activeHeader )
24110				._addClass( activeChildren, null, options.icons.header );
24111		}
24112
24113		if ( !clickedIsActive ) {
24114			this._removeClass( clicked, "ui-accordion-header-collapsed" )
24115				._addClass( clicked, "ui-accordion-header-active", "ui-state-active" );
24116			if ( options.icons ) {
24117				clickedChildren = clicked.children( ".ui-accordion-header-icon" );
24118				this._removeClass( clickedChildren, null, options.icons.header )
24119					._addClass( clickedChildren, null, options.icons.activeHeader );
24120			}
24121
24122			this._addClass( clicked.next(), "ui-accordion-content-active" );
24123		}
24124	},
24125
24126	_toggle: function( data ) {
24127		var toShow = data.newPanel,
24128			toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
24129
24130		// Handle activating a panel during the animation for another activation
24131		this.prevShow.add( this.prevHide ).stop( true, true );
24132		this.prevShow = toShow;
24133		this.prevHide = toHide;
24134
24135		if ( this.options.animate ) {
24136			this._animate( toShow, toHide, data );
24137		} else {
24138			toHide.hide();
24139			toShow.show();
24140			this._toggleComplete( data );
24141		}
24142
24143		toHide.attr( {
24144			"aria-hidden": "true"
24145		} );
24146		toHide.prev().attr( {
24147			"aria-selected": "false",
24148			"aria-expanded": "false"
24149		} );
24150
24151		// if we're switching panels, remove the old header from the tab order
24152		// if we're opening from collapsed state, remove the previous header from the tab order
24153		// if we're collapsing, then keep the collapsing header in the tab order
24154		if ( toShow.length && toHide.length ) {
24155			toHide.prev().attr( {
24156				"tabIndex": -1,
24157				"aria-expanded": "false"
24158			} );
24159		} else if ( toShow.length ) {
24160			this.headers.filter( function() {
24161				return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0;
24162			} )
24163				.attr( "tabIndex", -1 );
24164		}
24165
24166		toShow
24167			.attr( "aria-hidden", "false" )
24168			.prev()
24169				.attr( {
24170					"aria-selected": "true",
24171					"aria-expanded": "true",
24172					tabIndex: 0
24173				} );
24174	},
24175
24176	_animate: function( toShow, toHide, data ) {
24177		var total, easing, duration,
24178			that = this,
24179			adjust = 0,
24180			boxSizing = toShow.css( "box-sizing" ),
24181			down = toShow.length &&
24182				( !toHide.length || ( toShow.index() < toHide.index() ) ),
24183			animate = this.options.animate || {},
24184			options = down && animate.down || animate,
24185			complete = function() {
24186				that._toggleComplete( data );
24187			};
24188
24189		if ( typeof options === "number" ) {
24190			duration = options;
24191		}
24192		if ( typeof options === "string" ) {
24193			easing = options;
24194		}
24195
24196		// fall back from options to animation in case of partial down settings
24197		easing = easing || options.easing || animate.easing;
24198		duration = duration || options.duration || animate.duration;
24199
24200		if ( !toHide.length ) {
24201			return toShow.animate( this.showProps, duration, easing, complete );
24202		}
24203		if ( !toShow.length ) {
24204			return toHide.animate( this.hideProps, duration, easing, complete );
24205		}
24206
24207		total = toShow.show().outerHeight();
24208		toHide.animate( this.hideProps, {
24209			duration: duration,
24210			easing: easing,
24211			step: function( now, fx ) {
24212				fx.now = Math.round( now );
24213			}
24214		} );
24215		toShow
24216			.hide()
24217			.animate( this.showProps, {
24218				duration: duration,
24219				easing: easing,
24220				complete: complete,
24221				step: function( now, fx ) {
24222					fx.now = Math.round( now );
24223					if ( fx.prop !== "height" ) {
24224						if ( boxSizing === "content-box" ) {
24225							adjust += fx.now;
24226						}
24227					} else if ( that.options.heightStyle !== "content" ) {
24228						fx.now = Math.round( total - toHide.outerHeight() - adjust );
24229						adjust = 0;
24230					}
24231				}
24232			} );
24233	},
24234
24235	_toggleComplete: function( data ) {
24236		var toHide = data.oldPanel,
24237			prev = toHide.prev();
24238
24239		this._removeClass( toHide, "ui-accordion-content-active" );
24240		this._removeClass( prev, "ui-accordion-header-active" )
24241			._addClass( prev, "ui-accordion-header-collapsed" );
24242
24243		// Work around for rendering bug in IE (#5421)
24244		if ( toHide.length ) {
24245			toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;
24246		}
24247		this._trigger( "activate", null, data );
24248	}
24249} );
24250
24251} ) );
24252
24253
24254
24255
24256
24257
24258
24259/*!
24260 * jQuery UI Menu 1.12.1
24261 * http://jqueryui.com
24262 *
24263 * Copyright jQuery Foundation and other contributors
24264 * Released under the MIT license.
24265 * http://jquery.org/license
24266 */
24267
24268//>>label: Menu
24269//>>group: Widgets
24270//>>description: Creates nestable menus.
24271//>>docs: http://api.jqueryui.com/menu/
24272//>>demos: http://jqueryui.com/menu/
24273//>>css.structure: ../../themes/base/core.css
24274//>>css.structure: ../../themes/base/menu.css
24275//>>css.theme: ../../themes/base/theme.css
24276
24277( function( factory ) {
24278	if ( typeof define === "function" && define.amd ) {
24279
24280		// AMD. Register as an anonymous module.
24281		define( [
24282			"jquery",
24283			"../keycode",
24284			"../position",
24285			"../safe-active-element",
24286			"../unique-id",
24287			"../version",
24288			"../widget"
24289		], factory );
24290	} else {
24291
24292		// Browser globals
24293		factory( jQuery );
24294	}
24295}( function( $ ) {
24296
24297return
vendor: 17,045 bytes, lines 24297-24930
24297 $.widget( "ui.menu", {
24298	version: "1.12.1",
24299	defaultElement: "<ul>",
24300	delay: 300,
24301	options: {
24302		icons: {
24303			submenu: "ui-icon-caret-1-e"
24304		},
24305		items: "> *",
24306		menus: "ul",
24307		position: {
24308			my: "left top",
24309			at: "right top"
24310		},
24311		role: "menu",
24312
24313		// Callbacks
24314		blur: null,
24315		focus: null,
24316		select: null
24317	},
24318
24319	_create: function() {
24320		this.activeMenu = this.element;
24321
24322		// Flag used to prevent firing of the click handler
24323		// as the event bubbles up through nested menus
24324		this.mouseHandled = false;
24325		this.element
24326			.uniqueId()
24327			.attr( {
24328				role: this.options.role,
24329				tabIndex: 0
24330			} );
24331
24332		this._addClass( "ui-menu", "ui-widget ui-widget-content" );
24333		this._on( {
24334
24335			// Prevent focus from sticking to links inside menu after clicking
24336			// them (focus should always stay on UL during navigation).
24337			"mousedown .ui-menu-item": function( event ) {
24338				event.preventDefault();
24339			},
24340			"click .ui-menu-item": function( event ) {
24341				var target = $( event.target );
24342				var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
24343				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
24344					this.select( event );
24345
24346					// Only set the mouseHandled flag if the event will bubble, see #9469.
24347					if ( !event.isPropagationStopped() ) {
24348						this.mouseHandled = true;
24349					}
24350
24351					// Open submenu on click
24352					if ( target.has( ".ui-menu" ).length ) {
24353						this.expand( event );
24354					} else if ( !this.element.is( ":focus" ) &&
24355							active.closest( ".ui-menu" ).length ) {
24356
24357						// Redirect focus to the menu
24358						this.element.trigger( "focus", [ true ] );
24359
24360						// If the active item is on the top level, let it stay active.
24361						// Otherwise, blur the active item since it is no longer visible.
24362						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
24363							clearTimeout( this.timer );
24364						}
24365					}
24366				}
24367			},
24368			"mouseenter .ui-menu-item": function( event ) {
24369
24370				// Ignore mouse events while typeahead is active, see #10458.
24371				// Prevents focusing the wrong item when typeahead causes a scroll while the mouse
24372				// is over an item in the menu
24373				if ( this.previousFilter ) {
24374					return;
24375				}
24376
24377				var actualTarget = $( event.target ).closest( ".ui-menu-item" ),
24378					target = $( event.currentTarget );
24379
24380				// Ignore bubbled events on parent items, see #11641
24381				if ( actualTarget[ 0 ] !== target[ 0 ] ) {
24382					return;
24383				}
24384
24385				// Remove ui-state-active class from siblings of the newly focused menu item
24386				// to avoid a jump caused by adjacent elements both having a class with a border
24387				this._removeClass( target.siblings().children( ".ui-state-active" ),
24388					null, "ui-state-active" );
24389				this.focus( event, target );
24390			},
24391			mouseleave: "collapseAll",
24392			"mouseleave .ui-menu": "collapseAll",
24393			focus: function( event, keepActiveItem ) {
24394
24395				// If there's already an active item, keep it active
24396				// If not, activate the first item
24397				var item = this.active || this.element.find( this.options.items ).eq( 0 );
24398
24399				if ( !keepActiveItem ) {
24400					this.focus( event, item );
24401				}
24402			},
24403			blur: function( event ) {
24404				this._delay( function() {
24405					var notContained = !$.contains(
24406						this.element[ 0 ],
24407						$.ui.safeActiveElement( this.document[ 0 ] )
24408					);
24409					if ( notContained ) {
24410						this.collapseAll( event );
24411					}
24412				} );
24413			},
24414			keydown: "_keydown"
24415		} );
24416
24417		this.refresh();
24418
24419		// Clicks outside of a menu collapse any open menus
24420		this._on( this.document, {
24421			click: function( event ) {
24422				if ( this._closeOnDocumentClick( event ) ) {
24423					this.collapseAll( event );
24424				}
24425
24426				// Reset the mouseHandled flag
24427				this.mouseHandled = false;
24428			}
24429		} );
24430	},
24431
24432	_destroy: function() {
24433		var items = this.element.find( ".ui-menu-item" )
24434				.removeAttr( "role aria-disabled" ),
24435			submenus = items.children( ".ui-menu-item-wrapper" )
24436				.removeUniqueId()
24437				.removeAttr( "tabIndex role aria-haspopup" );
24438
24439		// Destroy (sub)menus
24440		this.element
24441			.removeAttr( "aria-activedescendant" )
24442			.find( ".ui-menu" ).addBack()
24443				.removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " +
24444					"tabIndex" )
24445				.removeUniqueId()
24446				.show();
24447
24448		submenus.children().each( function() {
24449			var elem = $( this );
24450			if ( elem.data( "ui-menu-submenu-caret" ) ) {
24451				elem.remove();
24452			}
24453		} );
24454	},
24455
24456	_keydown: function( event ) {
24457		var match, prev, character, skip,
24458			preventDefault = true;
24459
24460		switch ( event.keyCode ) {
24461		case $.ui.keyCode.PAGE_UP:
24462			this.previousPage( event );
24463			break;
24464		case $.ui.keyCode.PAGE_DOWN:
24465			this.nextPage( event );
24466			break;
24467		case $.ui.keyCode.HOME:
24468			this._move( "first", "first", event );
24469			break;
24470		case $.ui.keyCode.END:
24471			this._move( "last", "last", event );
24472			break;
24473		case $.ui.keyCode.UP:
24474			this.previous( event );
24475			break;
24476		case $.ui.keyCode.DOWN:
24477			this.next( event );
24478			break;
24479		case $.ui.keyCode.LEFT:
24480			this.collapse( event );
24481			break;
24482		case $.ui.keyCode.RIGHT:
24483			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
24484				this.expand( event );
24485			}
24486			break;
24487		case $.ui.keyCode.ENTER:
24488		case $.ui.keyCode.SPACE:
24489			this._activate( event );
24490			break;
24491		case $.ui.keyCode.ESCAPE:
24492			this.collapse( event );
24493			break;
24494		default:
24495			preventDefault = false;
24496			prev = this.previousFilter || "";
24497			skip = false;
24498
24499			// Support number pad values
24500			character = event.keyCode >= 96 && event.keyCode <= 105 ?
24501				( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );
24502
24503			clearTimeout( this.filterTimer );
24504
24505			if ( character === prev ) {
24506				skip = true;
24507			} else {
24508				character = prev + character;
24509			}
24510
24511			match = this._filterMenuItems( character );
24512			match = skip && match.index( this.active.next() ) !== -1 ?
24513				this.active.nextAll( ".ui-menu-item" ) :
24514				match;
24515
24516			// If no matches on the current filter, reset to the last character pressed
24517			// to move down the menu to the first item that starts with that character
24518			if ( !match.length ) {
24519				character = String.fromCharCode( event.keyCode );
24520				match = this._filterMenuItems( character );
24521			}
24522
24523			if ( match.length ) {
24524				this.focus( event, match );
24525				this.previousFilter = character;
24526				this.filterTimer = this._delay( function() {
24527					delete this.previousFilter;
24528				}, 1000 );
24529			} else {
24530				delete this.previousFilter;
24531			}
24532		}
24533
24534		if ( preventDefault ) {
24535			event.preventDefault();
24536		}
24537	},
24538
24539	_activate: function( event ) {
24540		if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
24541			if ( this.active.children( "[aria-haspopup='true']" ).length ) {
24542				this.expand( event );
24543			} else {
24544				this.select( event );
24545			}
24546		}
24547	},
24548
24549	refresh: function() {
24550		var menus, items, newSubmenus, newItems, newWrappers,
24551			that = this,
24552			icon = this.options.icons.submenu,
24553			submenus = this.element.find( this.options.menus );
24554
24555		this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
24556
24557		// Initialize nested menus
24558		newSubmenus = submenus.filter( ":not(.ui-menu)" )
24559			.hide()
24560			.attr( {
24561				role: this.options.role,
24562				"aria-hidden": "true",
24563				"aria-expanded": "false"
24564			} )
24565			.each( function() {
24566				var menu = $( this ),
24567					item = menu.prev(),
24568					submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );
24569
24570				that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
24571				item
24572					.attr( "aria-haspopup", "true" )
24573					.prepend( submenuCaret );
24574				menu.attr( "aria-labelledby", item.attr( "id" ) );
24575			} );
24576
24577		this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );
24578
24579		menus = submenus.add( this.element );
24580		items = menus.find( this.options.items );
24581
24582		// Initialize menu-items containing spaces and/or dashes only as dividers
24583		items.not( ".ui-menu-item" ).each( function() {
24584			var item = $( this );
24585			if ( that._isDivider( item ) ) {
24586				that._addClass( item, "ui-menu-divider", "ui-widget-content" );
24587			}
24588		} );
24589
24590		// Don't refresh list items that are already adapted
24591		newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
24592		newWrappers = newItems.children()
24593			.not( ".ui-menu" )
24594				.uniqueId()
24595				.attr( {
24596					tabIndex: -1,
24597					role: this._itemRole()
24598				} );
24599		this._addClass( newItems, "ui-menu-item" )
24600			._addClass( newWrappers, "ui-menu-item-wrapper" );
24601
24602		// Add aria-disabled attribute to any disabled menu item
24603		items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
24604
24605		// If the active item has been removed, blur the menu
24606		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
24607			this.blur();
24608		}
24609	},
24610
24611	_itemRole: function() {
24612		return {
24613			menu: "menuitem",
24614			listbox: "option"
24615		}[ this.options.role ];
24616	},
24617
24618	_setOption: function( key, value ) {
24619		if ( key === "icons" ) {
24620			var icons = this.element.find( ".ui-menu-icon" );
24621			this._removeClass( icons, null, this.options.icons.submenu )
24622				._addClass( icons, null, value.submenu );
24623		}
24624		this._super( key, value );
24625	},
24626
24627	_setOptionDisabled: function( value ) {
24628		this._super( value );
24629
24630		this.element.attr( "aria-disabled", String( value ) );
24631		this._toggleClass( null, "ui-state-disabled", !!value );
24632	},
24633
24634	focus: function( event, item ) {
24635		var nested, focused, activeParent;
24636		this.blur( event, event && event.type === "focus" );
24637
24638		this._scrollIntoView( item );
24639
24640		this.active = item.first();
24641
24642		focused = this.active.children( ".ui-menu-item-wrapper" );
24643		this._addClass( focused, null, "ui-state-active" );
24644
24645		// Only update aria-activedescendant if there's a role
24646		// otherwise we assume focus is managed elsewhere
24647		if ( this.options.role ) {
24648			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
24649		}
24650
24651		// Highlight active parent menu item, if any
24652		activeParent = this.active
24653			.parent()
24654				.closest( ".ui-menu-item" )
24655					.children( ".ui-menu-item-wrapper" );
24656		this._addClass( activeParent, null, "ui-state-active" );
24657
24658		if ( event && event.type === "keydown" ) {
24659			this._close();
24660		} else {
24661			this.timer = this._delay( function() {
24662				this._close();
24663			}, this.delay );
24664		}
24665
24666		nested = item.children( ".ui-menu" );
24667		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
24668			this._startOpening( nested );
24669		}
24670		this.activeMenu = item.parent();
24671
24672		this._trigger( "focus", event, { item: item } );
24673	},
24674
24675	_scrollIntoView: function( item ) {
24676		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
24677		if ( this._hasScroll() ) {
24678			borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
24679			paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
24680			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
24681			scroll = this.activeMenu.scrollTop();
24682			elementHeight = this.activeMenu.height();
24683			itemHeight = item.outerHeight();
24684
24685			if ( offset < 0 ) {
24686				this.activeMenu.scrollTop( scroll + offset );
24687			} else if ( offset + itemHeight > elementHeight ) {
24688				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
24689			}
24690		}
24691	},
24692
24693	blur: function( event, fromFocus ) {
24694		if ( !fromFocus ) {
24695			clearTimeout( this.timer );
24696		}
24697
24698		if ( !this.active ) {
24699			return;
24700		}
24701
24702		this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
24703			null, "ui-state-active" );
24704
24705		this._trigger( "blur", event, { item: this.active } );
24706		this.active = null;
24707	},
24708
24709	_startOpening: function( submenu ) {
24710		clearTimeout( this.timer );
24711
24712		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
24713		// shift in the submenu position when mousing over the caret icon
24714		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
24715			return;
24716		}
24717
24718		this.timer = this._delay( function() {
24719			this._close();
24720			this._open( submenu );
24721		}, this.delay );
24722	},
24723
24724	_open: function( submenu ) {
24725		var position = $.extend( {
24726			of: this.active
24727		}, this.options.position );
24728
24729		clearTimeout( this.timer );
24730		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
24731			.hide()
24732			.attr( "aria-hidden", "true" );
24733
24734		submenu
24735			.show()
24736			.removeAttr( "aria-hidden" )
24737			.attr( "aria-expanded", "true" )
24738			.position( position );
24739	},
24740
24741	collapseAll: function( event, all ) {
24742		clearTimeout( this.timer );
24743		this.timer = this._delay( function() {
24744
24745			// If we were passed an event, look for the submenu that contains the event
24746			var currentMenu = all ? this.element :
24747				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
24748
24749			// If we found no valid submenu ancestor, use the main menu to close all
24750			// sub menus anyway
24751			if ( !currentMenu.length ) {
24752				currentMenu = this.element;
24753			}
24754
24755			this._close( currentMenu );
24756
24757			this.blur( event );
24758
24759			// Work around active item staying active after menu is blurred
24760			this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" );
24761
24762			this.activeMenu = currentMenu;
24763		}, this.delay );
24764	},
24765
24766	// With no arguments, closes the currently active menu - if nothing is active
24767	// it closes all menus.  If passed an argument, it will search for menus BELOW
24768	_close: function( startMenu ) {
24769		if ( !startMenu ) {
24770			startMenu = this.active ? this.active.parent() : this.element;
24771		}
24772
24773		startMenu.find( ".ui-menu" )
24774			.hide()
24775			.attr( "aria-hidden", "true" )
24776			.attr( "aria-expanded", "false" );
24777	},
24778
24779	_closeOnDocumentClick: function( event ) {
24780		return !$( event.target ).closest( ".ui-menu" ).length;
24781	},
24782
24783	_isDivider: function( item ) {
24784
24785		// Match hyphen, em dash, en dash
24786		return !/[^\-\u2014\u2013\s]/.test( item.text() );
24787	},
24788
24789	collapse: function( event ) {
24790		var newItem = this.active &&
24791			this.active.parent().closest( ".ui-menu-item", this.element );
24792		if ( newItem && newItem.length ) {
24793			this._close();
24794			this.focus( event, newItem );
24795		}
24796	},
24797
24798	expand: function( event ) {
24799		var newItem = this.active &&
24800			this.active
24801				.children( ".ui-menu " )
24802					.find( this.options.items )
24803						.first();
24804
24805		if ( newItem && newItem.length ) {
24806			this._open( newItem.parent() );
24807
24808			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
24809			this._delay( function() {
24810				this.focus( event, newItem );
24811			} );
24812		}
24813	},
24814
24815	next: function( event ) {
24816		this._move( "next", "first", event );
24817	},
24818
24819	previous: function( event ) {
24820		this._move( "prev", "last", event );
24821	},
24822
24823	isFirstItem: function() {
24824		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
24825	},
24826
24827	isLastItem: function() {
24828		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
24829	},
24830
24831	_move: function( direction, filter, event ) {
24832		var next;
24833		if ( this.active ) {
24834			if ( direction === "first" || direction === "last" ) {
24835				next = this.active
24836					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
24837					.eq( -1 );
24838			} else {
24839				next = this.active
24840					[ direction + "All" ]( ".ui-menu-item" )
24841					.eq( 0 );
24842			}
24843		}
24844		if ( !next || !next.length || !this.active ) {
24845			next = this.activeMenu.find( this.options.items )[ filter ]();
24846		}
24847
24848		this.focus( event, next );
24849	},
24850
24851	nextPage: function( event ) {
24852		var item, base, height;
24853
24854		if ( !this.active ) {
24855			this.next( event );
24856			return;
24857		}
24858		if ( this.isLastItem() ) {
24859			return;
24860		}
24861		if ( this._hasScroll() ) {
24862			base = this.active.offset().top;
24863			height = this.element.height();
24864			this.active.nextAll( ".ui-menu-item" ).each( function() {
24865				item = $( this );
24866				return item.offset().top - base - height < 0;
24867			} );
24868
24869			this.focus( event, item );
24870		} else {
24871			this.focus( event, this.activeMenu.find( this.options.items )
24872				[ !this.active ? "first" : "last" ]() );
24873		}
24874	},
24875
24876	previousPage: function( event ) {
24877		var item, base, height;
24878		if ( !this.active ) {
24879			this.next( event );
24880			return;
24881		}
24882		if ( this.isFirstItem() ) {
24883			return;
24884		}
24885		if ( this._hasScroll() ) {
24886			base = this.active.offset().top;
24887			height = this.element.height();
24888			this.active.prevAll( ".ui-menu-item" ).each( function() {
24889				item = $( this );
24890				return item.offset().top - base + height > 0;
24891			} );
24892
24893			this.focus( event, item );
24894		} else {
24895			this.focus( event, this.activeMenu.find( this.options.items ).first() );
24896		}
24897	},
24898
24899	_hasScroll: function() {
24900		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
24901	},
24902
24903	select: function( event ) {
24904
24905		// TODO: It should never be possible to not have an active item at this
24906		// point, but the tests don't trigger mouseenter before click.
24907		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
24908		var ui = { item: this.active };
24909		if ( !this.active.has( ".ui-menu" ).length ) {
24910			this.collapseAll( event, true );
24911		}
24912		this._trigger( "select", event, ui );
24913	},
24914
24915	_filterMenuItems: function( character ) {
24916		var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
24917			regex = new RegExp( "^" + escapedCharacter, "i" );
24918
24919		return this.activeMenu
24920			.find( this.options.items )
24921
24922				// Only match on items, not dividers or other content (#10571)
24923				.filter( ".ui-menu-item" )
24924					.filter( function() {
24925						return regex.test(
24926							$.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) );
24927					} );
24928	}
24929} );
24930
24931} ) );
24932
24933
24934
24935
24936
24937
24938
24939/*!
24940 * jQuery UI Autocomplete 1.12.1
24941 * http://jqueryui.com
24942 *
24943 * Copyright jQuery Foundation and other contributors
24944 * Released under the MIT license.
24945 * http://jquery.org/license
24946 */
24947
24948//>>label: Autocomplete
24949//>>group: Widgets
24950//>>description: Lists suggested words as the user is typing.
24951//>>docs: http://api.jqueryui.com/autocomplete/
24952//>>demos: http://jqueryui.com/autocomplete/
24953//>>css.structure: ../../themes/base/core.css
24954//>>css.structure: ../../themes/base/autocomplete.css
24955//>>css.theme: ../../themes/base/theme.css
24956
24957( function( factory ) {
24958	if ( typeof define === "function" && define.amd ) {
24959
24960		// AMD. Register as an anonymous module.
24961		define( [
24962			"jquery",
24963			"./menu",
24964			"../keycode",
24965			"../position",
24966			"../safe-active-element",
24967			"../version",
24968			"../widget"
24969		], factory );
24970	} else {
24971
24972		// Browser globals
24973		factory( jQuery );
24974	}
24975}( function( $ ) {
vendor: 16,784 bytes, lines 24975-25617
24975
24976
24977$.widget( "ui.autocomplete", {
24978	version: "1.12.1",
24979	defaultElement: "<input>",
24980	options: {
24981		appendTo: null,
24982		autoFocus: false,
24983		delay: 300,
24984		minLength: 1,
24985		position: {
24986			my: "left top",
24987			at: "left bottom",
24988			collision: "none"
24989		},
24990		source: null,
24991
24992		// Callbacks
24993		change: null,
24994		close: null,
24995		focus: null,
24996		open: null,
24997		response: null,
24998		search: null,
24999		select: null
25000	},
25001
25002	requestIndex: 0,
25003	pending: 0,
25004
25005	_create: function() {
25006
25007		// Some browsers only repeat keydown events, not keypress events,
25008		// so we use the suppressKeyPress flag to determine if we've already
25009		// handled the keydown event. #7269
25010		// Unfortunately the code for & in keypress is the same as the up arrow,
25011		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
25012		// events when we know the keydown event was used to modify the
25013		// search term. #7799
25014		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
25015			nodeName = this.element[ 0 ].nodeName.toLowerCase(),
25016			isTextarea = nodeName === "textarea",
25017			isInput = nodeName === "input";
25018
25019		// Textareas are always multi-line
25020		// Inputs are always single-line, even if inside a contentEditable element
25021		// IE also treats inputs as contentEditable
25022		// All other element types are determined by whether or not they're contentEditable
25023		this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );
25024
25025		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
25026		this.isNewMenu = true;
25027
25028		this._addClass( "ui-autocomplete-input" );
25029		this.element.attr( "autocomplete", "off" );
25030
25031		this._on( this.element, {
25032			keydown: function( event ) {
25033				if ( this.element.prop( "readOnly" ) ) {
25034					suppressKeyPress = true;
25035					suppressInput = true;
25036					suppressKeyPressRepeat = true;
25037					return;
25038				}
25039
25040				suppressKeyPress = false;
25041				suppressInput = false;
25042				suppressKeyPressRepeat = false;
25043				var keyCode = $.ui.keyCode;
25044				switch ( event.keyCode ) {
25045				case keyCode.PAGE_UP:
25046					suppressKeyPress = true;
25047					this._move( "previousPage", event );
25048					break;
25049				case keyCode.PAGE_DOWN:
25050					suppressKeyPress = true;
25051					this._move( "nextPage", event );
25052					break;
25053				case keyCode.UP:
25054					suppressKeyPress = true;
25055					this._keyEvent( "previous", event );
25056					break;
25057				case keyCode.DOWN:
25058					suppressKeyPress = true;
25059					this._keyEvent( "next", event );
25060					break;
25061				case keyCode.ENTER:
25062
25063					// when menu is open and has focus
25064					if ( this.menu.active ) {
25065
25066						// #6055 - Opera still allows the keypress to occur
25067						// which causes forms to submit
25068						suppressKeyPress = true;
25069						event.preventDefault();
25070						this.menu.select( event );
25071					}
25072					break;
25073				case keyCode.TAB:
25074					if ( this.menu.active ) {
25075						this.menu.select( event );
25076					}
25077					break;
25078				case keyCode.ESCAPE:
25079					if ( this.menu.element.is( ":visible" ) ) {
25080						if ( !this.isMultiLine ) {
25081							this._value( this.term );
25082						}
25083						this.close( event );
25084
25085						// Different browsers have different default behavior for escape
25086						// Single press can mean undo or clear
25087						// Double press in IE means clear the whole form
25088						event.preventDefault();
25089					}
25090					break;
25091				default:
25092					suppressKeyPressRepeat = true;
25093
25094					// search timeout should be triggered before the input value is changed
25095					this._searchTimeout( event );
25096					break;
25097				}
25098			},
25099			keypress: function( event ) {
25100				if ( suppressKeyPress ) {
25101					suppressKeyPress = false;
25102					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
25103						event.preventDefault();
25104					}
25105					return;
25106				}
25107				if ( suppressKeyPressRepeat ) {
25108					return;
25109				}
25110
25111				// Replicate some key handlers to allow them to repeat in Firefox and Opera
25112				var keyCode = $.ui.keyCode;
25113				switch ( event.keyCode ) {
25114				case keyCode.PAGE_UP:
25115					this._move( "previousPage", event );
25116					break;
25117				case keyCode.PAGE_DOWN:
25118					this._move( "nextPage", event );
25119					break;
25120				case keyCode.UP:
25121					this._keyEvent( "previous", event );
25122					break;
25123				case keyCode.DOWN:
25124					this._keyEvent( "next", event );
25125					break;
25126				}
25127			},
25128			input: function( event ) {
25129				if ( suppressInput ) {
25130					suppressInput = false;
25131					event.preventDefault();
25132					return;
25133				}
25134				this._searchTimeout( event );
25135			},
25136			focus: function() {
25137				this.selectedItem = null;
25138				this.previous = this._value();
25139			},
25140			blur: function( event ) {
25141				if ( this.cancelBlur ) {
25142					delete this.cancelBlur;
25143					return;
25144				}
25145
25146				clearTimeout( this.searching );
25147				this.close( event );
25148				this._change( event );
25149			}
25150		} );
25151
25152		this._initSource();
25153		this.menu = $( "<ul>" )
25154			.appendTo( this._appendTo() )
25155			.menu( {
25156
25157				// disable ARIA support, the live region takes care of that
25158				role: null
25159			} )
25160			.hide()
25161			.menu( "instance" );
25162
25163		this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
25164		this._on( this.menu.element, {
25165			mousedown: function( event ) {
25166
25167				// prevent moving focus out of the text field
25168				event.preventDefault();
25169
25170				// IE doesn't prevent moving focus even with event.preventDefault()
25171				// so we set a flag to know when we should ignore the blur event
25172				this.cancelBlur = true;
25173				this._delay( function() {
25174					delete this.cancelBlur;
25175
25176					// Support: IE 8 only
25177					// Right clicking a menu item or selecting text from the menu items will
25178					// result in focus moving out of the input. However, we've already received
25179					// and ignored the blur event because of the cancelBlur flag set above. So
25180					// we restore focus to ensure that the menu closes properly based on the user's
25181					// next actions.
25182					if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
25183						this.element.trigger( "focus" );
25184					}
25185				} );
25186			},
25187			menufocus: function( event, ui ) {
25188				var label, item;
25189
25190				// support: Firefox
25191				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
25192				if ( this.isNewMenu ) {
25193					this.isNewMenu = false;
25194					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
25195						this.menu.blur();
25196
25197						this.document.one( "mousemove", function() {
25198							$( event.target ).trigger( event.originalEvent );
25199						} );
25200
25201						return;
25202					}
25203				}
25204
25205				item = ui.item.data( "ui-autocomplete-item" );
25206				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
25207
25208					// use value to match what will end up in the input, if it was a key event
25209					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
25210						this._value( item.value );
25211					}
25212				}
25213
25214				// Announce the value in the liveRegion
25215				label = ui.item.attr( "aria-label" ) || item.value;
25216				if ( label && $.trim( label ).length ) {
25217					this.liveRegion.children().hide();
25218					$( "<div>" ).text( label ).appendTo( this.liveRegion );
25219				}
25220			},
25221			menuselect: function( event, ui ) {
25222				var item = ui.item.data( "ui-autocomplete-item" ),
25223					previous = this.previous;
25224
25225				// Only trigger when focus was lost (click on menu)
25226				if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
25227					this.element.trigger( "focus" );
25228					this.previous = previous;
25229
25230					// #6109 - IE triggers two focus events and the second
25231					// is asynchronous, so we need to reset the previous
25232					// term synchronously and asynchronously :-(
25233					this._delay( function() {
25234						this.previous = previous;
25235						this.selectedItem = item;
25236					} );
25237				}
25238
25239				if ( false !== this._trigger( "select", event, { item: item } ) ) {
25240					this._value( item.value );
25241				}
25242
25243				// reset the term after the select event
25244				// this allows custom select handling to work properly
25245				this.term = this._value();
25246
25247				this.close( event );
25248				this.selectedItem = item;
25249			}
25250		} );
25251
25252		this.liveRegion = $( "<div>", {
25253			role: "status",
25254			"aria-live": "assertive",
25255			"aria-relevant": "additions"
25256		} )
25257			.appendTo( this.document[ 0 ].body );
25258
25259		this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
25260
25261		// Turning off autocomplete prevents the browser from remembering the
25262		// value when navigating through history, so we re-enable autocomplete
25263		// if the page is unloaded before the widget is destroyed. #7790
25264		this._on( this.window, {
25265			beforeunload: function() {
25266				this.element.removeAttr( "autocomplete" );
25267			}
25268		} );
25269	},
25270
25271	_destroy: function() {
25272		clearTimeout( this.searching );
25273		this.element.removeAttr( "autocomplete" );
25274		this.menu.element.remove();
25275		this.liveRegion.remove();
25276	},
25277
25278	_setOption: function( key, value ) {
25279		this._super( key, value );
25280		if ( key === "source" ) {
25281			this._initSource();
25282		}
25283		if ( key === "appendTo" ) {
25284			this.menu.element.appendTo( this._appendTo() );
25285		}
25286		if ( key === "disabled" && value && this.xhr ) {
25287			this.xhr.abort();
25288		}
25289	},
25290
25291	_isEventTargetInWidget: function( event ) {
25292		var menuElement = this.menu.element[ 0 ];
25293
25294		return event.target === this.element[ 0 ] ||
25295			event.target === menuElement ||
25296			$.contains( menuElement, event.target );
25297	},
25298
25299	_closeOnClickOutside: function( event ) {
25300		if ( !this._isEventTargetInWidget( event ) ) {
25301			this.close();
25302		}
25303	},
25304
25305	_appendTo: function() {
25306		var element = this.options.appendTo;
25307
25308		if ( element ) {
25309			element = element.jquery || element.nodeType ?
25310				$( element ) :
25311				this.document.find( element ).eq( 0 );
25312		}
25313
25314		if ( !element || !element[ 0 ] ) {
25315			element = this.element.closest( ".ui-front, dialog" );
25316		}
25317
25318		if ( !element.length ) {
25319			element = this.document[ 0 ].body;
25320		}
25321
25322		return element;
25323	},
25324
25325	_initSource: function() {
25326		var array, url,
25327			that = this;
25328		if ( $.isArray( this.options.source ) ) {
25329			array = this.options.source;
25330			this.source = function( request, response ) {
25331				response( $.ui.autocomplete.filter( array, request.term ) );
25332			};
25333		} else if ( typeof this.options.source === "string" ) {
25334			url = this.options.source;
25335			this.source = function( request, response ) {
25336				if ( that.xhr ) {
25337					that.xhr.abort();
25338				}
25339				that.xhr = $.ajax( {
25340					url: url,
25341					data: request,
25342					dataType: "json",
25343					success: function( data ) {
25344						response( data );
25345					},
25346					error: function() {
25347						response( [] );
25348					}
25349				} );
25350			};
25351		} else {
25352			this.source = this.options.source;
25353		}
25354	},
25355
25356	_searchTimeout: function( event ) {
25357		clearTimeout( this.searching );
25358		this.searching = this._delay( function() {
25359
25360			// Search if the value has changed, or if the user retypes the same value (see #7434)
25361			var equalValues = this.term === this._value(),
25362				menuVisible = this.menu.element.is( ":visible" ),
25363				modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
25364
25365			if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
25366				this.selectedItem = null;
25367				this.search( null, event );
25368			}
25369		}, this.options.delay );
25370	},
25371
25372	search: function( value, event ) {
25373		value = value != null ? value : this._value();
25374
25375		// Always save the actual value, not the one passed as an argument
25376		this.term = this._value();
25377
25378		if ( value.length < this.options.minLength ) {
25379			return this.close( event );
25380		}
25381
25382		if ( this._trigger( "search", event ) === false ) {
25383			return;
25384		}
25385
25386		return this._search( value );
25387	},
25388
25389	_search: function( value ) {
25390		this.pending++;
25391		this._addClass( "ui-autocomplete-loading" );
25392		this.cancelSearch = false;
25393
25394		this.source( { term: value }, this._response() );
25395	},
25396
25397	_response: function() {
25398		var index = ++this.requestIndex;
25399
25400		return $.proxy( function( content ) {
25401			if ( index === this.requestIndex ) {
25402				this.__response( content );
25403			}
25404
25405			this.pending--;
25406			if ( !this.pending ) {
25407				this._removeClass( "ui-autocomplete-loading" );
25408			}
25409		}, this );
25410	},
25411
25412	__response: function( content ) {
25413		if ( content ) {
25414			content = this._normalize( content );
25415		}
25416		this._trigger( "response", null, { content: content } );
25417		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
25418			this._suggest( content );
25419			this._trigger( "open" );
25420		} else {
25421
25422			// use ._close() instead of .close() so we don't cancel future searches
25423			this._close();
25424		}
25425	},
25426
25427	close: function( event ) {
25428		this.cancelSearch = true;
25429		this._close( event );
25430	},
25431
25432	_close: function( event ) {
25433
25434		// Remove the handler that closes the menu on outside clicks
25435		this._off( this.document, "mousedown" );
25436
25437		if ( this.menu.element.is( ":visible" ) ) {
25438			this.menu.element.hide();
25439			this.menu.blur();
25440			this.isNewMenu = true;
25441			this._trigger( "close", event );
25442		}
25443	},
25444
25445	_change: function( event ) {
25446		if ( this.previous !== this._value() ) {
25447			this._trigger( "change", event, { item: this.selectedItem } );
25448		}
25449	},
25450
25451	_normalize: function( items ) {
25452
25453		// assume all items have the right format when the first item is complete
25454		if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
25455			return items;
25456		}
25457		return $.map( items, function( item ) {
25458			if ( typeof item === "string" ) {
25459				return {
25460					label: item,
25461					value: item
25462				};
25463			}
25464			return $.extend( {}, item, {
25465				label: item.label || item.value,
25466				value: item.value || item.label
25467			} );
25468		} );
25469	},
25470
25471	_suggest: function( items ) {
25472		var ul = this.menu.element.empty();
25473		this._renderMenu( ul, items );
25474		this.isNewMenu = true;
25475		this.menu.refresh();
25476
25477		// Size and position menu
25478		ul.show();
25479		this._resizeMenu();
25480		ul.position( $.extend( {
25481			of: this.element
25482		}, this.options.position ) );
25483
25484		if ( this.options.autoFocus ) {
25485			this.menu.next();
25486		}
25487
25488		// Listen for interactions outside of the widget (#6642)
25489		this._on( this.document, {
25490			mousedown: "_closeOnClickOutside"
25491		} );
25492	},
25493
25494	_resizeMenu: function() {
25495		var ul = this.menu.element;
25496		ul.outerWidth( Math.max(
25497
25498			// Firefox wraps long text (possibly a rounding bug)
25499			// so we add 1px to avoid the wrapping (#7513)
25500			ul.width( "" ).outerWidth() + 1,
25501			this.element.outerWidth()
25502		) );
25503	},
25504
25505	_renderMenu: function( ul, items ) {
25506		var that = this;
25507		$.each( items, function( index, item ) {
25508			that._renderItemData( ul, item );
25509		} );
25510	},
25511
25512	_renderItemData: function( ul, item ) {
25513		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
25514	},
25515
25516	_renderItem: function( ul, item ) {
25517		return $( "<li>" )
25518			.append( $( "<div>" ).text( item.label ) )
25519			.appendTo( ul );
25520	},
25521
25522	_move: function( direction, event ) {
25523		if ( !this.menu.element.is( ":visible" ) ) {
25524			this.search( null, event );
25525			return;
25526		}
25527		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
25528				this.menu.isLastItem() && /^next/.test( direction ) ) {
25529
25530			if ( !this.isMultiLine ) {
25531				this._value( this.term );
25532			}
25533
25534			this.menu.blur();
25535			return;
25536		}
25537		this.menu[ direction ]( event );
25538	},
25539
25540	widget: function() {
25541		return this.menu.element;
25542	},
25543
25544	_value: function() {
25545		return this.valueMethod.apply( this.element, arguments );
25546	},
25547
25548	_keyEvent: function( keyEvent, event ) {
25549		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
25550			this._move( keyEvent, event );
25551
25552			// Prevents moving cursor to beginning/end of the text field in some browsers
25553			event.preventDefault();
25554		}
25555	},
25556
25557	// Support: Chrome <=50
25558	// We should be able to just use this.element.prop( "isContentEditable" )
25559	// but hidden elements always report false in Chrome.
25560	// https://code.google.com/p/chromium/issues/detail?id=313082
25561	_isContentEditable: function( element ) {
25562		if ( !element.length ) {
25563			return false;
25564		}
25565
25566		var editable = element.prop( "contentEditable" );
25567
25568		if ( editable === "inherit" ) {
25569		  return this._isContentEditable( element.parent() );
25570		}
25571
25572		return editable === "true";
25573	}
25574} );
25575
25576$.extend( $.ui.autocomplete, {
25577	escapeRegex: function( value ) {
25578		return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
25579	},
25580	filter: function( array, term ) {
25581		var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
25582		return $.grep( array, function( value ) {
25583			return matcher.test( value.label || value.value || value );
25584		} );
25585	}
25586} );
25587
25588// Live region extension, adding a `messages` option
25589// NOTE: This is an experimental API. We are still investigating
25590// a full solution for string manipulation and internationalization.
25591$.widget( "ui.autocomplete", $.ui.autocomplete, {
25592	options: {
25593		messages: {
25594			noResults: "No search results.",
25595			results: function( amount ) {
25596				return amount + ( amount > 1 ? " results are" : " result is" ) +
25597					" available, use up and down arrow keys to navigate.";
25598			}
25599		}
25600	},
25601
25602	__response: function( content ) {
25603		var message;
25604		this._superApply( arguments );
25605		if ( this.options.disabled || this.cancelSearch ) {
25606			return;
25607		}
25608		if ( content && content.length ) {
25609			message = this.options.messages.results( content.length );
25610		} else {
25611			message = this.options.messages.noResults;
25612		}
25613		this.liveRegion.children().hide();
25614		$( "<div>" ).text( message ).appendTo( this.liveRegion );
25615	}
25616} );
25617
vendor: 27,090 bytes, lines 25618-26640
25618return $.ui.autocomplete;
25619
25620} ) );
25621
25622
25623/*!
25624 * jQuery UI Controlgroup 1.12.1
25625 * http://jqueryui.com
25626 *
25627 * Copyright jQuery Foundation and other contributors
25628 * Released under the MIT license.
25629 * http://jquery.org/license
25630 */
25631
25632//>>label: Controlgroup
25633//>>group: Widgets
25634//>>description: Visually groups form control widgets
25635//>>docs: http://api.jqueryui.com/controlgroup/
25636//>>demos: http://jqueryui.com/controlgroup/
25637//>>css.structure: ../../themes/base/core.css
25638//>>css.structure: ../../themes/base/controlgroup.css
25639//>>css.theme: ../../themes/base/theme.css
25640
25641( function( factory ) {
25642	if ( typeof define === "function" && define.amd ) {
25643
25644		// AMD. Register as an anonymous module.
25645		define( [
25646			"jquery",
25647			"../widget"
25648		], factory );
25649	} else {
25650
25651		// Browser globals
25652		factory( jQuery );
25653	}
25654}( function( $ ) {
25655var controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g;
25656
25657return $.widget( "ui.controlgroup", {
25658	version: "1.12.1",
25659	defaultElement: "<div>",
25660	options: {
25661		direction: "horizontal",
25662		disabled: null,
25663		onlyVisible: true,
25664		items: {
25665			"button": "input[type=button], input[type=submit], input[type=reset], button, a",
25666			"controlgroupLabel": ".ui-controlgroup-label",
25667			"checkboxradio": "input[type='checkbox'], input[type='radio']",
25668			"selectmenu": "select",
25669			"spinner": ".ui-spinner-input"
25670		}
25671	},
25672
25673	_create: function() {
25674		this._enhance();
25675	},
25676
25677	// To support the enhanced option in jQuery Mobile, we isolate DOM manipulation
25678	_enhance: function() {
25679		this.element.attr( "role", "toolbar" );
25680		this.refresh();
25681	},
25682
25683	_destroy: function() {
25684		this._callChildMethod( "destroy" );
25685		this.childWidgets.removeData( "ui-controlgroup-data" );
25686		this.element.removeAttr( "role" );
25687		if ( this.options.items.controlgroupLabel ) {
25688			this.element
25689				.find( this.options.items.controlgroupLabel )
25690				.find( ".ui-controlgroup-label-contents" )
25691				.contents().unwrap();
25692		}
25693	},
25694
25695	_initWidgets: function() {
25696		var that = this,
25697			childWidgets = [];
25698
25699		// First we iterate over each of the items options
25700		$.each( this.options.items, function( widget, selector ) {
25701			var labels;
25702			var options = {};
25703
25704			// Make sure the widget has a selector set
25705			if ( !selector ) {
25706				return;
25707			}
25708
25709			if ( widget === "controlgroupLabel" ) {
25710				labels = that.element.find( selector );
25711				labels.each( function() {
25712					var element = $( this );
25713
25714					if ( element.children( ".ui-controlgroup-label-contents" ).length ) {
25715						return;
25716					}
25717					element.contents()
25718						.wrapAll( "<span class='ui-controlgroup-label-contents'></span>" );
25719				} );
25720				that._addClass( labels, null, "ui-widget ui-widget-content ui-state-default" );
25721				childWidgets = childWidgets.concat( labels.get() );
25722				return;
25723			}
25724
25725			// Make sure the widget actually exists
25726			if ( !$.fn[ widget ] ) {
25727				return;
25728			}
25729
25730			// We assume everything is in the middle to start because we can't determine
25731			// first / last elements until all enhancments are done.
25732			if ( that[ "_" + widget + "Options" ] ) {
25733				options = that[ "_" + widget + "Options" ]( "middle" );
25734			} else {
25735				options = { classes: {} };
25736			}
25737
25738			// Find instances of this widget inside controlgroup and init them
25739			that.element
25740				.find( selector )
25741				.each( function() {
25742					var element = $( this );
25743					var instance = element[ widget ]( "instance" );
25744
25745					// We need to clone the default options for this type of widget to avoid
25746					// polluting the variable options which has a wider scope than a single widget.
25747					var instanceOptions = $.widget.extend( {}, options );
25748
25749					// If the button is the child of a spinner ignore it
25750					// TODO: Find a more generic solution
25751					if ( widget === "button" && element.parent( ".ui-spinner" ).length ) {
25752						return;
25753					}
25754
25755					// Create the widget if it doesn't exist
25756					if ( !instance ) {
25757						instance = element[ widget ]()[ widget ]( "instance" );
25758					}
25759					if ( instance ) {
25760						instanceOptions.classes =
25761							that._resolveClassesValues( instanceOptions.classes, instance );
25762					}
25763					element[ widget ]( instanceOptions );
25764
25765					// Store an instance of the controlgroup to be able to reference
25766					// from the outermost element for changing options and refresh
25767					var widgetElement = element[ widget ]( "widget" );
25768					$.data( widgetElement[ 0 ], "ui-controlgroup-data",
25769						instance ? instance : element[ widget ]( "instance" ) );
25770
25771					childWidgets.push( widgetElement[ 0 ] );
25772				} );
25773		} );
25774
25775		this.childWidgets = $( $.unique( childWidgets ) );
25776		this._addClass( this.childWidgets, "ui-controlgroup-item" );
25777	},
25778
25779	_callChildMethod: function( method ) {
25780		this.childWidgets.each( function() {
25781			var element = $( this ),
25782				data = element.data( "ui-controlgroup-data" );
25783			if ( data && data[ method ] ) {
25784				data[ method ]();
25785			}
25786		} );
25787	},
25788
25789	_updateCornerClass: function( element, position ) {
25790		var remove = "ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all";
25791		var add = this._buildSimpleOptions( position, "label" ).classes.label;
25792
25793		this._removeClass( element, null, remove );
25794		this._addClass( element, null, add );
25795	},
25796
25797	_buildSimpleOptions: function( position, key ) {
25798		var direction = this.options.direction === "vertical";
25799		var result = {
25800			classes: {}
25801		};
25802		result.classes[ key ] = {
25803			"middle": "",
25804			"first": "ui-corner-" + ( direction ? "top" : "left" ),
25805			"last": "ui-corner-" + ( direction ? "bottom" : "right" ),
25806			"only": "ui-corner-all"
25807		}[ position ];
25808
25809		return result;
25810	},
25811
25812	_spinnerOptions: function( position ) {
25813		var options = this._buildSimpleOptions( position, "ui-spinner" );
25814
25815		options.classes[ "ui-spinner-up" ] = "";
25816		options.classes[ "ui-spinner-down" ] = "";
25817
25818		return options;
25819	},
25820
25821	_buttonOptions: function( position ) {
25822		return this._buildSimpleOptions( position, "ui-button" );
25823	},
25824
25825	_checkboxradioOptions: function( position ) {
25826		return this._buildSimpleOptions( position, "ui-checkboxradio-label" );
25827	},
25828
25829	_selectmenuOptions: function( position ) {
25830		var direction = this.options.direction === "vertical";
25831		return {
25832			width: direction ? "auto" : false,
25833			classes: {
25834				middle: {
25835					"ui-selectmenu-button-open": "",
25836					"ui-selectmenu-button-closed": ""
25837				},
25838				first: {
25839					"ui-selectmenu-button-open": "ui-corner-" + ( direction ? "top" : "tl" ),
25840					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "top" : "left" )
25841				},
25842				last: {
25843					"ui-selectmenu-button-open": direction ? "" : "ui-corner-tr",
25844					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "bottom" : "right" )
25845				},
25846				only: {
25847					"ui-selectmenu-button-open": "ui-corner-top",
25848					"ui-selectmenu-button-closed": "ui-corner-all"
25849				}
25850
25851			}[ position ]
25852		};
25853	},
25854
25855	_resolveClassesValues: function( classes, instance ) {
25856		var result = {};
25857		$.each( classes, function( key ) {
25858			var current = instance.options.classes[ key ] || "";
25859			current = $.trim( current.replace( controlgroupCornerRegex, "" ) );
25860			result[ key ] = ( current + " " + classes[ key ] ).replace( /\s+/g, " " );
25861		} );
25862		return result;
25863	},
25864
25865	_setOption: function( key, value ) {
25866		if ( key === "direction" ) {
25867			this._removeClass( "ui-controlgroup-" + this.options.direction );
25868		}
25869
25870		this._super( key, value );
25871		if ( key === "disabled" ) {
25872			this._callChildMethod( value ? "disable" : "enable" );
25873			return;
25874		}
25875
25876		this.refresh();
25877	},
25878
25879	refresh: function() {
25880		var children,
25881			that = this;
25882
25883		this._addClass( "ui-controlgroup ui-controlgroup-" + this.options.direction );
25884
25885		if ( this.options.direction === "horizontal" ) {
25886			this._addClass( null, "ui-helper-clearfix" );
25887		}
25888		this._initWidgets();
25889
25890		children = this.childWidgets;
25891
25892		// We filter here because we need to track all childWidgets not just the visible ones
25893		if ( this.options.onlyVisible ) {
25894			children = children.filter( ":visible" );
25895		}
25896
25897		if ( children.length ) {
25898
25899			// We do this last because we need to make sure all enhancment is done
25900			// before determining first and last
25901			$.each( [ "first", "last" ], function( index, value ) {
25902				var instance = children[ value ]().data( "ui-controlgroup-data" );
25903
25904				if ( instance && that[ "_" + instance.widgetName + "Options" ] ) {
25905					var options = that[ "_" + instance.widgetName + "Options" ](
25906						children.length === 1 ? "only" : value
25907					);
25908					options.classes = that._resolveClassesValues( options.classes, instance );
25909					instance.element[ instance.widgetName ]( options );
25910				} else {
25911					that._updateCornerClass( children[ value ](), value );
25912				}
25913			} );
25914
25915			// Finally call the refresh method on each of the child widgets.
25916			this._callChildMethod( "refresh" );
25917		}
25918	}
25919} );
25920} ) );
25921
25922
25923
25924
25925
25926/*!
25927 * jQuery UI Checkboxradio 1.12.1
25928 * http://jqueryui.com
25929 *
25930 * Copyright jQuery Foundation and other contributors
25931 * Released under the MIT license.
25932 * http://jquery.org/license
25933 */
25934
25935//>>label: Checkboxradio
25936//>>group: Widgets
25937//>>description: Enhances a form with multiple themeable checkboxes or radio buttons.
25938//>>docs: http://api.jqueryui.com/checkboxradio/
25939//>>demos: http://jqueryui.com/checkboxradio/
25940//>>css.structure: ../../themes/base/core.css
25941//>>css.structure: ../../themes/base/button.css
25942//>>css.structure: ../../themes/base/checkboxradio.css
25943//>>css.theme: ../../themes/base/theme.css
25944
25945( function( factory ) {
25946	if ( typeof define === "function" && define.amd ) {
25947
25948		// AMD. Register as an anonymous module.
25949		define( [
25950			"jquery",
25951			"../escape-selector",
25952			"../form-reset-mixin",
25953			"../labels",
25954			"../widget"
25955		], factory );
25956	} else {
25957
25958		// Browser globals
25959		factory( jQuery );
25960	}
25961}( function( $ ) {
25962
25963$.widget( "ui.checkboxradio", [ $.ui.formResetMixin, {
25964	version: "1.12.1",
25965	options: {
25966		disabled: null,
25967		label: null,
25968		icon: true,
25969		classes: {
25970			"ui-checkboxradio-label": "ui-corner-all",
25971			"ui-checkboxradio-icon": "ui-corner-all"
25972		}
25973	},
25974
25975	_getCreateOptions: function() {
25976		var disabled, labels;
25977		var that = this;
25978		var options = this._super() || {};
25979
25980		// We read the type here, because it makes more sense to throw a element type error first,
25981		// rather then the error for lack of a label. Often if its the wrong type, it
25982		// won't have a label (e.g. calling on a div, btn, etc)
25983		this._readType();
25984
25985		labels = this.element.labels();
25986
25987		// If there are multiple labels, use the last one
25988		this.label = $( labels[ labels.length - 1 ] );
25989		if ( !this.label.length ) {
25990			$.error( "No label found for checkboxradio widget" );
25991		}
25992
25993		this.originalLabel = "";
25994
25995		// We need to get the label text but this may also need to make sure it does not contain the
25996		// input itself.
25997		this.label.contents().not( this.element[ 0 ] ).each( function() {
25998
25999			// The label contents could be text, html, or a mix. We concat each element to get a
26000			// string representation of the label, without the input as part of it.
26001			that.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML;
26002		} );
26003
26004		// Set the label option if we found label text
26005		if ( this.originalLabel ) {
26006			options.label = this.originalLabel;
26007		}
26008
26009		disabled = this.element[ 0 ].disabled;
26010		if ( disabled != null ) {
26011			options.disabled = disabled;
26012		}
26013		return options;
26014	},
26015
26016	_create: function() {
26017		var checked = this.element[ 0 ].checked;
26018
26019		this._bindFormResetHandler();
26020
26021		if ( this.options.disabled == null ) {
26022			this.options.disabled = this.element[ 0 ].disabled;
26023		}
26024
26025		this._setOption( "disabled", this.options.disabled );
26026		this._addClass( "ui-checkboxradio", "ui-helper-hidden-accessible" );
26027		this._addClass( this.label, "ui-checkboxradio-label", "ui-button ui-widget" );
26028
26029		if ( this.type === "radio" ) {
26030			this._addClass( this.label, "ui-checkboxradio-radio-label" );
26031		}
26032
26033		if ( this.options.label && this.options.label !== this.originalLabel ) {
26034			this._updateLabel();
26035		} else if ( this.originalLabel ) {
26036			this.options.label = this.originalLabel;
26037		}
26038
26039		this._enhance();
26040
26041		if ( checked ) {
26042			this._addClass( this.label, "ui-checkboxradio-checked", "ui-state-active" );
26043			if ( this.icon ) {
26044				this._addClass( this.icon, null, "ui-state-hover" );
26045			}
26046		}
26047
26048		this._on( {
26049			change: "_toggleClasses",
26050			focus: function() {
26051				this._addClass( this.label, null, "ui-state-focus ui-visual-focus" );
26052			},
26053			blur: function() {
26054				this._removeClass( this.label, null, "ui-state-focus ui-visual-focus" );
26055			}
26056		} );
26057	},
26058
26059	_readType: function() {
26060		var nodeName = this.element[ 0 ].nodeName.toLowerCase();
26061		this.type = this.element[ 0 ].type;
26062		if ( nodeName !== "input" || !/radio|checkbox/.test( this.type ) ) {
26063			$.error( "Can't create checkboxradio on element.nodeName=" + nodeName +
26064				" and element.type=" + this.type );
26065		}
26066	},
26067
26068	// Support jQuery Mobile enhanced option
26069	_enhance: function() {
26070		this._updateIcon( this.element[ 0 ].checked );
26071	},
26072
26073	widget: function() {
26074		return this.label;
26075	},
26076
26077	_getRadioGroup: function() {
26078		var group;
26079		var name = this.element[ 0 ].name;
26080		var nameSelector = "input[name='" + $.ui.escapeSelector( name ) + "']";
26081
26082		if ( !name ) {
26083			return $( [] );
26084		}
26085
26086		if ( this.form.length ) {
26087			group = $( this.form[ 0 ].elements ).filter( nameSelector );
26088		} else {
26089
26090			// Not inside a form, check all inputs that also are not inside a form
26091			group = $( nameSelector ).filter( function() {
26092				return $( this ).form().length === 0;
26093			} );
26094		}
26095
26096		return group.not( this.element );
26097	},
26098
26099	_toggleClasses: function() {
26100		var checked = this.element[ 0 ].checked;
26101		this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked );
26102
26103		if ( this.options.icon && this.type === "checkbox" ) {
26104			this._toggleClass( this.icon, null, "ui-icon-check ui-state-checked", checked )
26105				._toggleClass( this.icon, null, "ui-icon-blank", !checked );
26106		}
26107
26108		if ( this.type === "radio" ) {
26109			this._getRadioGroup()
26110				.each( function() {
26111					var instance = $( this ).checkboxradio( "instance" );
26112
26113					if ( instance ) {
26114						instance._removeClass( instance.label,
26115							"ui-checkboxradio-checked", "ui-state-active" );
26116					}
26117				} );
26118		}
26119	},
26120
26121	_destroy: function() {
26122		this._unbindFormResetHandler();
26123
26124		if ( this.icon ) {
26125			this.icon.remove();
26126			this.iconSpace.remove();
26127		}
26128	},
26129
26130	_setOption: function( key, value ) {
26131
26132		// We don't allow the value to be set to nothing
26133		if ( key === "label" && !value ) {
26134			return;
26135		}
26136
26137		this._super( key, value );
26138
26139		if ( key === "disabled" ) {
26140			this._toggleClass( this.label, null, "ui-state-disabled", value );
26141			this.element[ 0 ].disabled = value;
26142
26143			// Don't refresh when setting disabled
26144			return;
26145		}
26146		this.refresh();
26147	},
26148
26149	_updateIcon: function( checked ) {
26150		var toAdd = "ui-icon ui-icon-background ";
26151
26152		if ( this.options.icon ) {
26153			if ( !this.icon ) {
26154				this.icon = $( "<span>" );
26155				this.iconSpace = $( "<span> </span>" );
26156				this._addClass( this.iconSpace, "ui-checkboxradio-icon-space" );
26157			}
26158
26159			if ( this.type === "checkbox" ) {
26160				toAdd += checked ? "ui-icon-check ui-state-checked" : "ui-icon-blank";
26161				this._removeClass( this.icon, null, checked ? "ui-icon-blank" : "ui-icon-check" );
26162			} else {
26163				toAdd += "ui-icon-blank";
26164			}
26165			this._addClass( this.icon, "ui-checkboxradio-icon", toAdd );
26166			if ( !checked ) {
26167				this._removeClass( this.icon, null, "ui-icon-check ui-state-checked" );
26168			}
26169			this.icon.prependTo( this.label ).after( this.iconSpace );
26170		} else if ( this.icon !== undefined ) {
26171			this.icon.remove();
26172			this.iconSpace.remove();
26173			delete this.icon;
26174		}
26175	},
26176
26177	_updateLabel: function() {
26178
26179		// Remove the contents of the label ( minus the icon, icon space, and input )
26180		var contents = this.label.contents().not( this.element[ 0 ] );
26181		if ( this.icon ) {
26182			contents = contents.not( this.icon[ 0 ] );
26183		}
26184		if ( this.iconSpace ) {
26185			contents = contents.not( this.iconSpace[ 0 ] );
26186		}
26187		contents.remove();
26188
26189		this.label.append( this.options.label );
26190	},
26191
26192	refresh: function() {
26193		var checked = this.element[ 0 ].checked,
26194			isDisabled = this.element[ 0 ].disabled;
26195
26196		this._updateIcon( checked );
26197		this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked );
26198		if ( this.options.label !== null ) {
26199			this._updateLabel();
26200		}
26201
26202		if ( isDisabled !== this.options.disabled ) {
26203			this._setOptions( { "disabled": isDisabled } );
26204		}
26205	}
26206
26207} ] );
26208
26209return $.ui.checkboxradio;
26210
26211} ) );
26212
26213
26214
26215
26216
26217/*!
26218 * jQuery UI Button 1.12.1
26219 * http://jqueryui.com
26220 *
26221 * Copyright jQuery Foundation and other contributors
26222 * Released under the MIT license.
26223 * http://jquery.org/license
26224 */
26225
26226//>>label: Button
26227//>>group: Widgets
26228//>>description: Enhances a form with themeable buttons.
26229//>>docs: http://api.jqueryui.com/button/
26230//>>demos: http://jqueryui.com/button/
26231//>>css.structure: ../../themes/base/core.css
26232//>>css.structure: ../../themes/base/button.css
26233//>>css.theme: ../../themes/base/theme.css
26234
26235( function( factory ) {
26236	if ( typeof define === "function" && define.amd ) {
26237
26238		// AMD. Register as an anonymous module.
26239		define( [
26240			"jquery",
26241
26242			// These are only for backcompat
26243			// TODO: Remove after 1.12
26244			"./controlgroup",
26245			"./checkboxradio",
26246
26247			"../keycode",
26248			"../widget"
26249		], factory );
26250	} else {
26251
26252		// Browser globals
26253		factory( jQuery );
26254	}
26255}( function( $ ) {
26256
26257$.widget( "ui.button", {
26258	version: "1.12.1",
26259	defaultElement: "<button>",
26260	options: {
26261		classes: {
26262			"ui-button": "ui-corner-all"
26263		},
26264		disabled: null,
26265		icon: null,
26266		iconPosition: "beginning",
26267		label: null,
26268		showLabel: true
26269	},
26270
26271	_getCreateOptions: function() {
26272		var disabled,
26273
26274			// This is to support cases like in jQuery Mobile where the base widget does have
26275			// an implementation of _getCreateOptions
26276			options = this._super() || {};
26277
26278		this.isInput = this.element.is( "input" );
26279
26280		disabled = this.element[ 0 ].disabled;
26281		if ( disabled != null ) {
26282			options.disabled = disabled;
26283		}
26284
26285		this.originalLabel = this.isInput ? this.element.val() : this.element.html();
26286		if ( this.originalLabel ) {
26287			options.label = this.originalLabel;
26288		}
26289
26290		return options;
26291	},
26292
26293	_create: function() {
26294		if ( !this.option.showLabel & !this.options.icon ) {
26295			this.options.showLabel = true;
26296		}
26297
26298		// We have to check the option again here even though we did in _getCreateOptions,
26299		// because null may have been passed on init which would override what was set in
26300		// _getCreateOptions
26301		if ( this.options.disabled == null ) {
26302			this.options.disabled = this.element[ 0 ].disabled || false;
26303		}
26304
26305		this.hasTitle = !!this.element.attr( "title" );
26306
26307		// Check to see if the label needs to be set or if its already correct
26308		if ( this.options.label && this.options.label !== this.originalLabel ) {
26309			if ( this.isInput ) {
26310				this.element.val( this.options.label );
26311			} else {
26312				this.element.html( this.options.label );
26313			}
26314		}
26315		this._addClass( "ui-button", "ui-widget" );
26316		this._setOption( "disabled", this.options.disabled );
26317		this._enhance();
26318
26319		if ( this.element.is( "a" ) ) {
26320			this._on( {
26321				"keyup": function( event ) {
26322					if ( event.keyCode === $.ui.keyCode.SPACE ) {
26323						event.preventDefault();
26324
26325						// Support: PhantomJS <= 1.9, IE 8 Only
26326						// If a native click is available use it so we actually cause navigation
26327						// otherwise just trigger a click event
26328						if ( this.element[ 0 ].click ) {
26329							this.element[ 0 ].click();
26330						} else {
26331							this.element.trigger( "click" );
26332						}
26333					}
26334				}
26335			} );
26336		}
26337	},
26338
26339	_enhance: function() {
26340		if ( !this.element.is( "button" ) ) {
26341			this.element.attr( "role", "button" );
26342		}
26343
26344		if ( this.options.icon ) {
26345			this._updateIcon( "icon", this.options.icon );
26346			this._updateTooltip();
26347		}
26348	},
26349
26350	_updateTooltip: function() {
26351		this.title = this.element.attr( "title" );
26352
26353		if ( !this.options.showLabel && !this.title ) {
26354			this.element.attr( "title", this.options.label );
26355		}
26356	},
26357
26358	_updateIcon: function( option, value ) {
26359		var icon = option !== "iconPosition",
26360			position = icon ? this.options.iconPosition : value,
26361			displayBlock = position === "top" || position === "bottom";
26362
26363		// Create icon
26364		if ( !this.icon ) {
26365			this.icon = $( "<span>" );
26366
26367			this._addClass( this.icon, "ui-button-icon", "ui-icon" );
26368
26369			if ( !this.options.showLabel ) {
26370				this._addClass( "ui-button-icon-only" );
26371			}
26372		} else if ( icon ) {
26373
26374			// If we are updating the icon remove the old icon class
26375			this._removeClass( this.icon, null, this.options.icon );
26376		}
26377
26378		// If we are updating the icon add the new icon class
26379		if ( icon ) {
26380			this._addClass( this.icon, null, value );
26381		}
26382
26383		this._attachIcon( position );
26384
26385		// If the icon is on top or bottom we need to add the ui-widget-icon-block class and remove
26386		// the iconSpace if there is one.
26387		if ( displayBlock ) {
26388			this._addClass( this.icon, null, "ui-widget-icon-block" );
26389			if ( this.iconSpace ) {
26390				this.iconSpace.remove();
26391			}
26392		} else {
26393
26394			// Position is beginning or end so remove the ui-widget-icon-block class and add the
26395			// space if it does not exist
26396			if ( !this.iconSpace ) {
26397				this.iconSpace = $( "<span> </span>" );
26398				this._addClass( this.iconSpace, "ui-button-icon-space" );
26399			}
26400			this._removeClass( this.icon, null, "ui-wiget-icon-block" );
26401			this._attachIconSpace( position );
26402		}
26403	},
26404
26405	_destroy: function() {
26406		this.element.removeAttr( "role" );
26407
26408		if ( this.icon ) {
26409			this.icon.remove();
26410		}
26411		if ( this.iconSpace ) {
26412			this.iconSpace.remove();
26413		}
26414		if ( !this.hasTitle ) {
26415			this.element.removeAttr( "title" );
26416		}
26417	},
26418
26419	_attachIconSpace: function( iconPosition ) {
26420		this.icon[ /^(?:end|bottom)/.test( iconPosition ) ? "before" : "after" ]( this.iconSpace );
26421	},
26422
26423	_attachIcon: function( iconPosition ) {
26424		this.element[ /^(?:end|bottom)/.test( iconPosition ) ? "append" : "prepend" ]( this.icon );
26425	},
26426
26427	_setOptions: function( options ) {
26428		var newShowLabel = options.showLabel === undefined ?
26429				this.options.showLabel :
26430				options.showLabel,
26431			newIcon = options.icon === undefined ? this.options.icon : options.icon;
26432
26433		if ( !newShowLabel && !newIcon ) {
26434			options.showLabel = true;
26435		}
26436		this._super( options );
26437	},
26438
26439	_setOption: function( key, value ) {
26440		if ( key === "icon" ) {
26441			if ( value ) {
26442				this._updateIcon( key, value );
26443			} else if ( this.icon ) {
26444				this.icon.remove();
26445				if ( this.iconSpace ) {
26446					this.iconSpace.remove();
26447				}
26448			}
26449		}
26450
26451		if ( key === "iconPosition" ) {
26452			this._updateIcon( key, value );
26453		}
26454
26455		// Make sure we can't end up with a button that has neither text nor icon
26456		if ( key === "showLabel" ) {
26457				this._toggleClass( "ui-button-icon-only", null, !value );
26458				this._updateTooltip();
26459		}
26460
26461		if ( key === "label" ) {
26462			if ( this.isInput ) {
26463				this.element.val( value );
26464			} else {
26465
26466				// If there is an icon, append it, else nothing then append the value
26467				// this avoids removal of the icon when setting label text
26468				this.element.html( value );
26469				if ( this.icon ) {
26470					this._attachIcon( this.options.iconPosition );
26471					this._attachIconSpace( this.options.iconPosition );
26472				}
26473			}
26474		}
26475
26476		this._super( key, value );
26477
26478		if ( key === "disabled" ) {
26479			this._toggleClass( null, "ui-state-disabled", value );
26480			this.element[ 0 ].disabled = value;
26481			if ( value ) {
26482				this.element.blur();
26483			}
26484		}
26485	},
26486
26487	refresh: function() {
26488
26489		// Make sure to only check disabled if its an element that supports this otherwise
26490		// check for the disabled class to determine state
26491		var isDisabled = this.element.is( "input, button" ) ?
26492			this.element[ 0 ].disabled : this.element.hasClass( "ui-button-disabled" );
26493
26494		if ( isDisabled !== this.options.disabled ) {
26495			this._setOptions( { disabled: isDisabled } );
26496		}
26497
26498		this._updateTooltip();
26499	}
26500} );
26501
26502// DEPRECATED
26503if ( $.uiBackCompat !== false ) {
26504
26505	// Text and Icons options
26506	$.widget( "ui.button", $.ui.button, {
26507		options: {
26508			text: true,
26509			icons: {
26510				primary: null,
26511				secondary: null
26512			}
26513		},
26514
26515		_create: function() {
26516			if ( this.options.showLabel && !this.options.text ) {
26517				this.options.showLabel = this.options.text;
26518			}
26519			if ( !this.options.showLabel && this.options.text ) {
26520				this.options.text = this.options.showLabel;
26521			}
26522			if ( !this.options.icon && ( this.options.icons.primary ||
26523					this.options.icons.secondary ) ) {
26524				if ( this.options.icons.primary ) {
26525					this.options.icon = this.options.icons.primary;
26526				} else {
26527					this.options.icon = this.options.icons.secondary;
26528					this.options.iconPosition = "end";
26529				}
26530			} else if ( this.options.icon ) {
26531				this.options.icons.primary = this.options.icon;
26532			}
26533			this._super();
26534		},
26535
26536		_setOption: function( key, value ) {
26537			if ( key === "text" ) {
26538				this._super( "showLabel", value );
26539				return;
26540			}
26541			if ( key === "showLabel" ) {
26542				this.options.text = value;
26543			}
26544			if ( key === "icon" ) {
26545				this.options.icons.primary = value;
26546			}
26547			if ( key === "icons" ) {
26548				if ( value.primary ) {
26549					this._super( "icon", value.primary );
26550					this._super( "iconPosition", "beginning" );
26551				} else if ( value.secondary ) {
26552					this._super( "icon", value.secondary );
26553					this._super( "iconPosition", "end" );
26554				}
26555			}
26556			this._superApply( arguments );
26557		}
26558	} );
26559
26560	$.fn.button = ( function( orig ) {
26561		return function() {
26562			if ( !this.length || ( this.length && this[ 0 ].tagName !== "INPUT" ) ||
26563					( this.length && this[ 0 ].tagName === "INPUT" && (
26564						this.attr( "type" ) !== "checkbox" && this.attr( "type" ) !== "radio"
26565					) ) ) {
26566				return orig.apply( this, arguments );
26567			}
26568			if ( !$.ui.checkboxradio ) {
26569				$.error( "Checkboxradio widget missing" );
26570			}
26571			if ( arguments.length === 0 ) {
26572				return this.checkboxradio( {
26573					"icon": false
26574				} );
26575			}
26576			return this.checkboxradio.apply( this, arguments );
26577		};
26578	} )( $.fn.button );
26579
26580	$.fn.buttonset = function() {
26581		if ( !$.ui.controlgroup ) {
26582			$.error( "Controlgroup widget missing" );
26583		}
26584		if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" && arguments[ 2 ] ) {
26585			return this.controlgroup.apply( this,
26586				[ arguments[ 0 ], "items.button", arguments[ 2 ] ] );
26587		}
26588		if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" ) {
26589			return this.controlgroup.apply( this, [ arguments[ 0 ], "items.button" ] );
26590		}
26591		if ( typeof arguments[ 0 ] === "object" && arguments[ 0 ].items ) {
26592			arguments[ 0 ].items = {
26593				button: arguments[ 0 ].items
26594			};
26595		}
26596		return this.controlgroup.apply( this, arguments );
26597	};
26598}
26599
26600return $.ui.button;
26601
26602} ) );
26603
26604
26605
26606// jscs:disable maximumLineLength
26607/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
26608/*!
26609 * jQuery UI Datepicker 1.12.1
26610 * http://jqueryui.com
26611 *
26612 * Copyright jQuery Foundation and other contributors
26613 * Released under the MIT license.
26614 * http://jquery.org/license
26615 */
26616
26617//>>label: Datepicker
26618//>>group: Widgets
26619//>>description: Displays a calendar from an input or inline for selecting dates.
26620//>>docs: http://api.jqueryui.com/datepicker/
26621//>>demos: http://jqueryui.com/datepicker/
26622//>>css.structure: ../../themes/base/core.css
26623//>>css.structure: ../../themes/base/datepicker.css
26624//>>css.theme: ../../themes/base/theme.css
26625
26626( function( factory ) {
26627	if ( typeof define === "function" && define.amd ) {
26628
26629		// AMD. Register as an anonymous module.
26630		define( [
26631			"jquery",
26632			"../version",
26633			"../keycode"
26634		], factory );
26635	} else {
26636
26637		// Browser globals
26638		factory( jQuery );
26639	}
26640}( function( $ ) {
vendor: 79,864 bytes, lines 26640-28722
26640
26641
26642$.extend( $.ui, { datepicker: { version: "1.12.1" } } );
26643
26644var datepicker_instActive;
26645
26646function datepicker_getZindex( elem ) {
26647	var position, value;
26648	while ( elem.length && elem[ 0 ] !== document ) {
26649
26650		// Ignore z-index if position is set to a value where z-index is ignored by the browser
26651		// This makes behavior of this function consistent across browsers
26652		// WebKit always returns auto if the element is positioned
26653		position = elem.css( "position" );
26654		if ( position === "absolute" || position === "relative" || position === "fixed" ) {
26655
26656			// IE returns 0 when zIndex is not specified
26657			// other browsers return a string
26658			// we ignore the case of nested elements with an explicit value of 0
26659			// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
26660			value = parseInt( elem.css( "zIndex" ), 10 );
26661			if ( !isNaN( value ) && value !== 0 ) {
26662				return value;
26663			}
26664		}
26665		elem = elem.parent();
26666	}
26667
26668	return 0;
26669}
26670/* Date picker manager.
26671   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
26672   Settings for (groups of) date pickers are maintained in an instance object,
26673   allowing multiple different settings on the same page. */
26674
26675function Datepicker() {
26676	this._curInst = null; // The current instance in use
26677	this._keyEvent = false; // If the last event was a key event
26678	this._disabledInputs = []; // List of date picker inputs that have been disabled
26679	this._datepickerShowing = false; // True if the popup picker is showing , false if not
26680	this._inDialog = false; // True if showing within a "dialog", false if not
26681	this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division
26682	this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class
26683	this._appendClass = "ui-datepicker-append"; // The name of the append marker class
26684	this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class
26685	this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class
26686	this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class
26687	this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class
26688	this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
26689	this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
26690	this.regional = []; // Available regional settings, indexed by language code
26691	this.regional[ "" ] = { // Default regional settings
26692		closeText: "Done", // Display text for close link
26693		prevText: "Prev", // Display text for previous month link
26694		nextText: "Next", // Display text for next month link
26695		currentText: "Today", // Display text for current month link
26696		monthNames: [ "January","February","March","April","May","June",
26697			"July","August","September","October","November","December" ], // Names of months for drop-down and formatting
26698		monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting
26699		dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting
26700		dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting
26701		dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], // Column headings for days starting at Sunday
26702		weekHeader: "Wk", // Column header for week of the year
26703		dateFormat: "mm/dd/yy", // See format options on parseDate
26704		firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
26705		isRTL: false, // True if right-to-left language, false if left-to-right
26706		showMonthAfterYear: false, // True if the year select precedes month, false for month then year
26707		yearSuffix: "" // Additional text to append to the year in the month headers
26708	};
26709	this._defaults = { // Global defaults for all the date picker instances
26710		showOn: "focus", // "focus" for popup on focus,
26711			// "button" for trigger button, or "both" for either
26712		showAnim: "fadeIn", // Name of jQuery animation for popup
26713		showOptions: {}, // Options for enhanced animations
26714		defaultDate: null, // Used when field is blank: actual date,
26715			// +/-number for offset from today, null for today
26716		appendText: "", // Display text following the input box, e.g. showing the format
26717		buttonText: "...", // Text for trigger button
26718		buttonImage: "", // URL for trigger button image
26719		buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
26720		hideIfNoPrevNext: false, // True to hide next/previous month links
26721			// if not applicable, false to just disable them
26722		navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
26723		gotoCurrent: false, // True if today link goes back to current selection instead
26724		changeMonth: false, // True if month can be selected directly, false if only prev/next
26725		changeYear: false, // True if year can be selected directly, false if only prev/next
26726		yearRange: "c-10:c+10", // Range of years to display in drop-down,
26727			// either relative to today's year (-nn:+nn), relative to currently displayed year
26728			// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
26729		showOtherMonths: false, // True to show dates in other months, false to leave blank
26730		selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
26731		showWeek: false, // True to show week of the year, false to not show it
26732		calculateWeek: this.iso8601Week, // How to calculate the week of the year,
26733			// takes a Date and returns the number of the week for it
26734		shortYearCutoff: "+10", // Short year values < this are in the current century,
26735			// > this are in the previous century,
26736			// string value starting with "+" for current year + value
26737		minDate: null, // The earliest selectable date, or null for no limit
26738		maxDate: null, // The latest selectable date, or null for no limit
26739		duration: "fast", // Duration of display/closure
26740		beforeShowDay: null, // Function that takes a date and returns an array with
26741			// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "",
26742			// [2] = cell title (optional), e.g. $.datepicker.noWeekends
26743		beforeShow: null, // Function that takes an input field and
26744			// returns a set of custom settings for the date picker
26745		onSelect: null, // Define a callback function when a date is selected
26746		onChangeMonthYear: null, // Define a callback function when the month or year is changed
26747		onClose: null, // Define a callback function when the datepicker is closed
26748		numberOfMonths: 1, // Number of months to show at a time
26749		showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
26750		stepMonths: 1, // Number of months to step back/forward
26751		stepBigMonths: 12, // Number of months to step back/forward for the big links
26752		altField: "", // Selector for an alternate field to store selected dates into
26753		altFormat: "", // The date format to use for the alternate field
26754		constrainInput: true, // The input is constrained by the current date format
26755		showButtonPanel: false, // True to show button panel, false to not show it
26756		autoSize: false, // True to size the input for the date format, false to leave as is
26757		disabled: false // The initial disabled state
26758	};
26759	$.extend( this._defaults, this.regional[ "" ] );
26760	this.regional.en = $.extend( true, {}, this.regional[ "" ] );
26761	this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en );
26762	this.dpDiv = datepicker_bindHover( $( "<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) );
26763}
26764
26765$.extend( Datepicker.prototype, {
26766	/* Class name added to elements to indicate already configured with a date picker. */
26767	markerClassName: "hasDatepicker",
26768
26769	//Keep track of the maximum number of rows displayed (see #7043)
26770	maxRows: 4,
26771
26772	// TODO rename to "widget" when switching to widget factory
26773	_widgetDatepicker: function() {
26774		return this.dpDiv;
26775	},
26776
26777	/* Override the default settings for all instances of the date picker.
26778	 * @param  settings  object - the new settings to use as defaults (anonymous object)
26779	 * @return the manager object
26780	 */
26781	setDefaults: function( settings ) {
26782		datepicker_extendRemove( this._defaults, settings || {} );
26783		return this;
26784	},
26785
26786	/* Attach the date picker to a jQuery selection.
26787	 * @param  target	element - the target input field or division or span
26788	 * @param  settings  object - the new settings to use for this date picker instance (anonymous)
26789	 */
26790	_attachDatepicker: function( target, settings ) {
26791		var nodeName, inline, inst;
26792		nodeName = target.nodeName.toLowerCase();
26793		inline = ( nodeName === "div" || nodeName === "span" );
26794		if ( !target.id ) {
26795			this.uuid += 1;
26796			target.id = "dp" + this.uuid;
26797		}
26798		inst = this._newInst( $( target ), inline );
26799		inst.settings = $.extend( {}, settings || {} );
26800		if ( nodeName === "input" ) {
26801			this._connectDatepicker( target, inst );
26802		} else if ( inline ) {
26803			this._inlineDatepicker( target, inst );
26804		}
26805	},
26806
26807	/* Create a new instance object. */
26808	_newInst: function( target, inline ) {
26809		var id = target[ 0 ].id.replace( /([^A-Za-z0-9_\-])/g, "\\\\$1" ); // escape jQuery meta chars
26810		return { id: id, input: target, // associated target
26811			selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
26812			drawMonth: 0, drawYear: 0, // month being drawn
26813			inline: inline, // is datepicker inline or not
26814			dpDiv: ( !inline ? this.dpDiv : // presentation div
26815			datepicker_bindHover( $( "<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ) ) };
26816	},
26817
26818	/* Attach the date picker to an input field. */
26819	_connectDatepicker: function( target, inst ) {
26820		var input = $( target );
26821		inst.append = $( [] );
26822		inst.trigger = $( [] );
26823		if ( input.hasClass( this.markerClassName ) ) {
26824			return;
26825		}
26826		this._attachments( input, inst );
26827		input.addClass( this.markerClassName ).on( "keydown", this._doKeyDown ).
26828			on( "keypress", this._doKeyPress ).on( "keyup", this._doKeyUp );
26829		this._autoSize( inst );
26830		$.data( target, "datepicker", inst );
26831
26832		//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
26833		if ( inst.settings.disabled ) {
26834			this._disableDatepicker( target );
26835		}
26836	},
26837
26838	/* Make attachments based on settings. */
26839	_attachments: function( input, inst ) {
26840		var showOn, buttonText, buttonImage,
26841			appendText = this._get( inst, "appendText" ),
26842			isRTL = this._get( inst, "isRTL" );
26843
26844		if ( inst.append ) {
26845			inst.append.remove();
26846		}
26847		if ( appendText ) {
26848			inst.append = $( "<span class='" + this._appendClass + "'>" + appendText + "</span>" );
26849			input[ isRTL ? "before" : "after" ]( inst.append );
26850		}
26851
26852		input.off( "focus", this._showDatepicker );
26853
26854		if ( inst.trigger ) {
26855			inst.trigger.remove();
26856		}
26857
26858		showOn = this._get( inst, "showOn" );
26859		if ( showOn === "focus" || showOn === "both" ) { // pop-up date picker when in the marked field
26860			input.on( "focus", this._showDatepicker );
26861		}
26862		if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked
26863			buttonText = this._get( inst, "buttonText" );
26864			buttonImage = this._get( inst, "buttonImage" );
26865			inst.trigger = $( this._get( inst, "buttonImageOnly" ) ?
26866				$( "<img/>" ).addClass( this._triggerClass ).
26867					attr( { src: buttonImage, alt: buttonText, title: buttonText } ) :
26868				$( "<button type='button'></button>" ).addClass( this._triggerClass ).
26869					html( !buttonImage ? buttonText : $( "<img/>" ).attr(
26870					{ src:buttonImage, alt:buttonText, title:buttonText } ) ) );
26871			input[ isRTL ? "before" : "after" ]( inst.trigger );
26872			inst.trigger.on( "click", function() {
26873				if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) {
26874					$.datepicker._hideDatepicker();
26875				} else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) {
26876					$.datepicker._hideDatepicker();
26877					$.datepicker._showDatepicker( input[ 0 ] );
26878				} else {
26879					$.datepicker._showDatepicker( input[ 0 ] );
26880				}
26881				return false;
26882			} );
26883		}
26884	},
26885
26886	/* Apply the maximum length for the date format. */
26887	_autoSize: function( inst ) {
26888		if ( this._get( inst, "autoSize" ) && !inst.inline ) {
26889			var findMax, max, maxI, i,
26890				date = new Date( 2009, 12 - 1, 20 ), // Ensure double digits
26891				dateFormat = this._get( inst, "dateFormat" );
26892
26893			if ( dateFormat.match( /[DM]/ ) ) {
26894				findMax = function( names ) {
26895					max = 0;
26896					maxI = 0;
26897					for ( i = 0; i < names.length; i++ ) {
26898						if ( names[ i ].length > max ) {
26899							max = names[ i ].length;
26900							maxI = i;
26901						}
26902					}
26903					return maxI;
26904				};
26905				date.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ?
26906					"monthNames" : "monthNamesShort" ) ) ) );
26907				date.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ?
26908					"dayNames" : "dayNamesShort" ) ) ) + 20 - date.getDay() );
26909			}
26910			inst.input.attr( "size", this._formatDate( inst, date ).length );
26911		}
26912	},
26913
26914	/* Attach an inline date picker to a div. */
26915	_inlineDatepicker: function( target, inst ) {
26916		var divSpan = $( target );
26917		if ( divSpan.hasClass( this.markerClassName ) ) {
26918			return;
26919		}
26920		divSpan.addClass( this.markerClassName ).append( inst.dpDiv );
26921		$.data( target, "datepicker", inst );
26922		this._setDate( inst, this._getDefaultDate( inst ), true );
26923		this._updateDatepicker( inst );
26924		this._updateAlternate( inst );
26925
26926		//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
26927		if ( inst.settings.disabled ) {
26928			this._disableDatepicker( target );
26929		}
26930
26931		// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
26932		// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
26933		inst.dpDiv.css( "display", "block" );
26934	},
26935
26936	/* Pop-up the date picker in a "dialog" box.
26937	 * @param  input element - ignored
26938	 * @param  date	string or Date - the initial date to display
26939	 * @param  onSelect  function - the function to call when a date is selected
26940	 * @param  settings  object - update the dialog date picker instance's settings (anonymous object)
26941	 * @param  pos int[2] - coordinates for the dialog's position within the screen or
26942	 *					event - with x/y coordinates or
26943	 *					leave empty for default (screen centre)
26944	 * @return the manager object
26945	 */
26946	_dialogDatepicker: function( input, date, onSelect, settings, pos ) {
26947		var id, browserWidth, browserHeight, scrollX, scrollY,
26948			inst = this._dialogInst; // internal instance
26949
26950		if ( !inst ) {
26951			this.uuid += 1;
26952			id = "dp" + this.uuid;
26953			this._dialogInput = $( "<input type='text' id='" + id +
26954				"' style='position: absolute; top: -100px; width: 0px;'/>" );
26955			this._dialogInput.on( "keydown", this._doKeyDown );
26956			$( "body" ).append( this._dialogInput );
26957			inst = this._dialogInst = this._newInst( this._dialogInput, false );
26958			inst.settings = {};
26959			$.data( this._dialogInput[ 0 ], "datepicker", inst );
26960		}
26961		datepicker_extendRemove( inst.settings, settings || {} );
26962		date = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date );
26963		this._dialogInput.val( date );
26964
26965		this._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null );
26966		if ( !this._pos ) {
26967			browserWidth = document.documentElement.clientWidth;
26968			browserHeight = document.documentElement.clientHeight;
26969			scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
26970			scrollY = document.documentElement.scrollTop || document.body.scrollTop;
26971			this._pos = // should use actual width/height below
26972				[ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ];
26973		}
26974
26975		// Move input on screen for focus, but hidden behind dialog
26976		this._dialogInput.css( "left", ( this._pos[ 0 ] + 20 ) + "px" ).css( "top", this._pos[ 1 ] + "px" );
26977		inst.settings.onSelect = onSelect;
26978		this._inDialog = true;
26979		this.dpDiv.addClass( this._dialogClass );
26980		this._showDatepicker( this._dialogInput[ 0 ] );
26981		if ( $.blockUI ) {
26982			$.blockUI( this.dpDiv );
26983		}
26984		$.data( this._dialogInput[ 0 ], "datepicker", inst );
26985		return this;
26986	},
26987
26988	/* Detach a datepicker from its control.
26989	 * @param  target	element - the target input field or division or span
26990	 */
26991	_destroyDatepicker: function( target ) {
26992		var nodeName,
26993			$target = $( target ),
26994			inst = $.data( target, "datepicker" );
26995
26996		if ( !$target.hasClass( this.markerClassName ) ) {
26997			return;
26998		}
26999
27000		nodeName = target.nodeName.toLowerCase();
27001		$.removeData( target, "datepicker" );
27002		if ( nodeName === "input" ) {
27003			inst.append.remove();
27004			inst.trigger.remove();
27005			$target.removeClass( this.markerClassName ).
27006				off( "focus", this._showDatepicker ).
27007				off( "keydown", this._doKeyDown ).
27008				off( "keypress", this._doKeyPress ).
27009				off( "keyup", this._doKeyUp );
27010		} else if ( nodeName === "div" || nodeName === "span" ) {
27011			$target.removeClass( this.markerClassName ).empty();
27012		}
27013
27014		if ( datepicker_instActive === inst ) {
27015			datepicker_instActive = null;
27016		}
27017	},
27018
27019	/* Enable the date picker to a jQuery selection.
27020	 * @param  target	element - the target input field or division or span
27021	 */
27022	_enableDatepicker: function( target ) {
27023		var nodeName, inline,
27024			$target = $( target ),
27025			inst = $.data( target, "datepicker" );
27026
27027		if ( !$target.hasClass( this.markerClassName ) ) {
27028			return;
27029		}
27030
27031		nodeName = target.nodeName.toLowerCase();
27032		if ( nodeName === "input" ) {
27033			target.disabled = false;
27034			inst.trigger.filter( "button" ).
27035				each( function() { this.disabled = false; } ).end().
27036				filter( "img" ).css( { opacity: "1.0", cursor: "" } );
27037		} else if ( nodeName === "div" || nodeName === "span" ) {
27038			inline = $target.children( "." + this._inlineClass );
27039			inline.children().removeClass( "ui-state-disabled" );
27040			inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ).
27041				prop( "disabled", false );
27042		}
27043		this._disabledInputs = $.map( this._disabledInputs,
27044			function( value ) { return ( value === target ? null : value ); } ); // delete entry
27045	},
27046
27047	/* Disable the date picker to a jQuery selection.
27048	 * @param  target	element - the target input field or division or span
27049	 */
27050	_disableDatepicker: function( target ) {
27051		var nodeName, inline,
27052			$target = $( target ),
27053			inst = $.data( target, "datepicker" );
27054
27055		if ( !$target.hasClass( this.markerClassName ) ) {
27056			return;
27057		}
27058
27059		nodeName = target.nodeName.toLowerCase();
27060		if ( nodeName === "input" ) {
27061			target.disabled = true;
27062			inst.trigger.filter( "button" ).
27063				each( function() { this.disabled = true; } ).end().
27064				filter( "img" ).css( { opacity: "0.5", cursor: "default" } );
27065		} else if ( nodeName === "div" || nodeName === "span" ) {
27066			inline = $target.children( "." + this._inlineClass );
27067			inline.children().addClass( "ui-state-disabled" );
27068			inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ).
27069				prop( "disabled", true );
27070		}
27071		this._disabledInputs = $.map( this._disabledInputs,
27072			function( value ) { return ( value === target ? null : value ); } ); // delete entry
27073		this._disabledInputs[ this._disabledInputs.length ] = target;
27074	},
27075
27076	/* Is the first field in a jQuery collection disabled as a datepicker?
27077	 * @param  target	element - the target input field or division or span
27078	 * @return boolean - true if disabled, false if enabled
27079	 */
27080	_isDisabledDatepicker: function( target ) {
27081		if ( !target ) {
27082			return false;
27083		}
27084		for ( var i = 0; i < this._disabledInputs.length; i++ ) {
27085			if ( this._disabledInputs[ i ] === target ) {
27086				return true;
27087			}
27088		}
27089		return false;
27090	},
27091
27092	/* Retrieve the instance data for the target control.
27093	 * @param  target  element - the target input field or division or span
27094	 * @return  object - the associated instance data
27095	 * @throws  error if a jQuery problem getting data
27096	 */
27097	_getInst: function( target ) {
27098		try {
27099			return $.data( target, "datepicker" );
27100		}
27101		catch ( err ) {
27102			throw "Missing instance data for this datepicker";
27103		}
27104	},
27105
27106	/* Update or retrieve the settings for a date picker attached to an input field or division.
27107	 * @param  target  element - the target input field or division or span
27108	 * @param  name	object - the new settings to update or
27109	 *				string - the name of the setting to change or retrieve,
27110	 *				when retrieving also "all" for all instance settings or
27111	 *				"defaults" for all global defaults
27112	 * @param  value   any - the new value for the setting
27113	 *				(omit if above is an object or to retrieve a value)
27114	 */
27115	_optionDatepicker: function( target, name, value ) {
27116		var settings, date, minDate, maxDate,
27117			inst = this._getInst( target );
27118
27119		if ( arguments.length === 2 && typeof name === "string" ) {
27120			return ( name === "defaults" ? $.extend( {}, $.datepicker._defaults ) :
27121				( inst ? ( name === "all" ? $.extend( {}, inst.settings ) :
27122				this._get( inst, name ) ) : null ) );
27123		}
27124
27125		settings = name || {};
27126		if ( typeof name === "string" ) {
27127			settings = {};
27128			settings[ name ] = value;
27129		}
27130
27131		if ( inst ) {
27132			if ( this._curInst === inst ) {
27133				this._hideDatepicker();
27134			}
27135
27136			date = this._getDateDatepicker( target, true );
27137			minDate = this._getMinMaxDate( inst, "min" );
27138			maxDate = this._getMinMaxDate( inst, "max" );
27139			datepicker_extendRemove( inst.settings, settings );
27140
27141			// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
27142			if ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) {
27143				inst.settings.minDate = this._formatDate( inst, minDate );
27144			}
27145			if ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) {
27146				inst.settings.maxDate = this._formatDate( inst, maxDate );
27147			}
27148			if ( "disabled" in settings ) {
27149				if ( settings.disabled ) {
27150					this._disableDatepicker( target );
27151				} else {
27152					this._enableDatepicker( target );
27153				}
27154			}
27155			this._attachments( $( target ), inst );
27156			this._autoSize( inst );
27157			this._setDate( inst, date );
27158			this._updateAlternate( inst );
27159			this._updateDatepicker( inst );
27160		}
27161	},
27162
27163	// Change method deprecated
27164	_changeDatepicker: function( target, name, value ) {
27165		this._optionDatepicker( target, name, value );
27166	},
27167
27168	/* Redraw the date picker attached to an input field or division.
27169	 * @param  target  element - the target input field or division or span
27170	 */
27171	_refreshDatepicker: function( target ) {
27172		var inst = this._getInst( target );
27173		if ( inst ) {
27174			this._updateDatepicker( inst );
27175		}
27176	},
27177
27178	/* Set the dates for a jQuery selection.
27179	 * @param  target element - the target input field or division or span
27180	 * @param  date	Date - the new date
27181	 */
27182	_setDateDatepicker: function( target, date ) {
27183		var inst = this._getInst( target );
27184		if ( inst ) {
27185			this._setDate( inst, date );
27186			this._updateDatepicker( inst );
27187			this._updateAlternate( inst );
27188		}
27189	},
27190
27191	/* Get the date(s) for the first entry in a jQuery selection.
27192	 * @param  target element - the target input field or division or span
27193	 * @param  noDefault boolean - true if no default date is to be used
27194	 * @return Date - the current date
27195	 */
27196	_getDateDatepicker: function( target, noDefault ) {
27197		var inst = this._getInst( target );
27198		if ( inst && !inst.inline ) {
27199			this._setDateFromField( inst, noDefault );
27200		}
27201		return ( inst ? this._getDate( inst ) : null );
27202	},
27203
27204	/* Handle keystrokes. */
27205	_doKeyDown: function( event ) {
27206		var onSelect, dateStr, sel,
27207			inst = $.datepicker._getInst( event.target ),
27208			handled = true,
27209			isRTL = inst.dpDiv.is( ".ui-datepicker-rtl" );
27210
27211		inst._keyEvent = true;
27212		if ( $.datepicker._datepickerShowing ) {
27213			switch ( event.keyCode ) {
27214				case 9: $.datepicker._hideDatepicker();
27215						handled = false;
27216						break; // hide on tab out
27217				case 13: sel = $( "td." + $.datepicker._dayOverClass + ":not(." +
27218									$.datepicker._currentClass + ")", inst.dpDiv );
27219						if ( sel[ 0 ] ) {
27220							$.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] );
27221						}
27222
27223						onSelect = $.datepicker._get( inst, "onSelect" );
27224						if ( onSelect ) {
27225							dateStr = $.datepicker._formatDate( inst );
27226
27227							// Trigger custom callback
27228							onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );
27229						} else {
27230							$.datepicker._hideDatepicker();
27231						}
27232
27233						return false; // don't submit the form
27234				case 27: $.datepicker._hideDatepicker();
27235						break; // hide on escape
27236				case 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?
27237							-$.datepicker._get( inst, "stepBigMonths" ) :
27238							-$.datepicker._get( inst, "stepMonths" ) ), "M" );
27239						break; // previous month/year on page up/+ ctrl
27240				case 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?
27241							+$.datepicker._get( inst, "stepBigMonths" ) :
27242							+$.datepicker._get( inst, "stepMonths" ) ), "M" );
27243						break; // next month/year on page down/+ ctrl
27244				case 35: if ( event.ctrlKey || event.metaKey ) {
27245							$.datepicker._clearDate( event.target );
27246						}
27247						handled = event.ctrlKey || event.metaKey;
27248						break; // clear on ctrl or command +end
27249				case 36: if ( event.ctrlKey || event.metaKey ) {
27250							$.datepicker._gotoToday( event.target );
27251						}
27252						handled = event.ctrlKey || event.metaKey;
27253						break; // current on ctrl or command +home
27254				case 37: if ( event.ctrlKey || event.metaKey ) {
27255							$.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), "D" );
27256						}
27257						handled = event.ctrlKey || event.metaKey;
27258
27259						// -1 day on ctrl or command +left
27260						if ( event.originalEvent.altKey ) {
27261							$.datepicker._adjustDate( event.target, ( event.ctrlKey ?
27262								-$.datepicker._get( inst, "stepBigMonths" ) :
27263								-$.datepicker._get( inst, "stepMonths" ) ), "M" );
27264						}
27265
27266						// next month/year on alt +left on Mac
27267						break;
27268				case 38: if ( event.ctrlKey || event.metaKey ) {
27269							$.datepicker._adjustDate( event.target, -7, "D" );
27270						}
27271						handled = event.ctrlKey || event.metaKey;
27272						break; // -1 week on ctrl or command +up
27273				case 39: if ( event.ctrlKey || event.metaKey ) {
27274							$.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), "D" );
27275						}
27276						handled = event.ctrlKey || event.metaKey;
27277
27278						// +1 day on ctrl or command +right
27279						if ( event.originalEvent.altKey ) {
27280							$.datepicker._adjustDate( event.target, ( event.ctrlKey ?
27281								+$.datepicker._get( inst, "stepBigMonths" ) :
27282								+$.datepicker._get( inst, "stepMonths" ) ), "M" );
27283						}
27284
27285						// next month/year on alt +right
27286						break;
27287				case 40: if ( event.ctrlKey || event.metaKey ) {
27288							$.datepicker._adjustDate( event.target, +7, "D" );
27289						}
27290						handled = event.ctrlKey || event.metaKey;
27291						break; // +1 week on ctrl or command +down
27292				default: handled = false;
27293			}
27294		} else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home
27295			$.datepicker._showDatepicker( this );
27296		} else {
27297			handled = false;
27298		}
27299
27300		if ( handled ) {
27301			event.preventDefault();
27302			event.stopPropagation();
27303		}
27304	},
27305
27306	/* Filter entered characters - based on date format. */
27307	_doKeyPress: function( event ) {
27308		var chars, chr,
27309			inst = $.datepicker._getInst( event.target );
27310
27311		if ( $.datepicker._get( inst, "constrainInput" ) ) {
27312			chars = $.datepicker._possibleChars( $.datepicker._get( inst, "dateFormat" ) );
27313			chr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode );
27314			return event.ctrlKey || event.metaKey || ( chr < " " || !chars || chars.indexOf( chr ) > -1 );
27315		}
27316	},
27317
27318	/* Synchronise manual entry and field/alternate field. */
27319	_doKeyUp: function( event ) {
27320		var date,
27321			inst = $.datepicker._getInst( event.target );
27322
27323		if ( inst.input.val() !== inst.lastVal ) {
27324			try {
27325				date = $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ),
27326					( inst.input ? inst.input.val() : null ),
27327					$.datepicker._getFormatConfig( inst ) );
27328
27329				if ( date ) { // only if valid
27330					$.datepicker._setDateFromField( inst );
27331					$.datepicker._updateAlternate( inst );
27332					$.datepicker._updateDatepicker( inst );
27333				}
27334			}
27335			catch ( err ) {
27336			}
27337		}
27338		return true;
27339	},
27340
27341	/* Pop-up the date picker for a given input field.
27342	 * If false returned from beforeShow event handler do not show.
27343	 * @param  input  element - the input field attached to the date picker or
27344	 *					event - if triggered by focus
27345	 */
27346	_showDatepicker: function( input ) {
27347		input = input.target || input;
27348		if ( input.nodeName.toLowerCase() !== "input" ) { // find from button/image trigger
27349			input = $( "input", input.parentNode )[ 0 ];
27350		}
27351
27352		if ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here
27353			return;
27354		}
27355
27356		var inst, beforeShow, beforeShowSettings, isFixed,
27357			offset, showAnim, duration;
27358
27359		inst = $.datepicker._getInst( input );
27360		if ( $.datepicker._curInst && $.datepicker._curInst !== inst ) {
27361			$.datepicker._curInst.dpDiv.stop( true, true );
27362			if ( inst && $.datepicker._datepickerShowing ) {
27363				$.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] );
27364			}
27365		}
27366
27367		beforeShow = $.datepicker._get( inst, "beforeShow" );
27368		beforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {};
27369		if ( beforeShowSettings === false ) {
27370			return;
27371		}
27372		datepicker_extendRemove( inst.settings, beforeShowSettings );
27373
27374		inst.lastVal = null;
27375		$.datepicker._lastInput = input;
27376		$.datepicker._setDateFromField( inst );
27377
27378		if ( $.datepicker._inDialog ) { // hide cursor
27379			input.value = "";
27380		}
27381		if ( !$.datepicker._pos ) { // position below input
27382			$.datepicker._pos = $.datepicker._findPos( input );
27383			$.datepicker._pos[ 1 ] += input.offsetHeight; // add the height
27384		}
27385
27386		isFixed = false;
27387		$( input ).parents().each( function() {
27388			isFixed |= $( this ).css( "position" ) === "fixed";
27389			return !isFixed;
27390		} );
27391
27392		offset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] };
27393		$.datepicker._pos = null;
27394
27395		//to avoid flashes on Firefox
27396		inst.dpDiv.empty();
27397
27398		// determine sizing offscreen
27399		inst.dpDiv.css( { position: "absolute", display: "block", top: "-1000px" } );
27400		$.datepicker._updateDatepicker( inst );
27401
27402		// fix width for dynamic number of date pickers
27403		// and adjust position before showing
27404		offset = $.datepicker._checkOffset( inst, offset, isFixed );
27405		inst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ?
27406			"static" : ( isFixed ? "fixed" : "absolute" ) ), display: "none",
27407			left: offset.left + "px", top: offset.top + "px" } );
27408
27409		if ( !inst.inline ) {
27410			showAnim = $.datepicker._get( inst, "showAnim" );
27411			duration = $.datepicker._get( inst, "duration" );
27412			inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 );
27413			$.datepicker._datepickerShowing = true;
27414
27415			if ( $.effects && $.effects.effect[ showAnim ] ) {
27416				inst.dpDiv.show( showAnim, $.datepicker._get( inst, "showOptions" ), duration );
27417			} else {
27418				inst.dpDiv[ showAnim || "show" ]( showAnim ? duration : null );
27419			}
27420
27421			if ( $.datepicker._shouldFocusInput( inst ) ) {
27422				inst.input.trigger( "focus" );
27423			}
27424
27425			$.datepicker._curInst = inst;
27426		}
27427	},
27428
27429	/* Generate the date picker content. */
27430	_updateDatepicker: function( inst ) {
27431		this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
27432		datepicker_instActive = inst; // for delegate hover events
27433		inst.dpDiv.empty().append( this._generateHTML( inst ) );
27434		this._attachHandlers( inst );
27435
27436		var origyearshtml,
27437			numMonths = this._getNumberOfMonths( inst ),
27438			cols = numMonths[ 1 ],
27439			width = 17,
27440			activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" );
27441
27442		if ( activeCell.length > 0 ) {
27443			datepicker_handleMouseover.apply( activeCell.get( 0 ) );
27444		}
27445
27446		inst.dpDiv.removeClass( "ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4" ).width( "" );
27447		if ( cols > 1 ) {
27448			inst.dpDiv.addClass( "ui-datepicker-multi-" + cols ).css( "width", ( width * cols ) + "em" );
27449		}
27450		inst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? "add" : "remove" ) +
27451			"Class" ]( "ui-datepicker-multi" );
27452		inst.dpDiv[ ( this._get( inst, "isRTL" ) ? "add" : "remove" ) +
27453			"Class" ]( "ui-datepicker-rtl" );
27454
27455		if ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {
27456			inst.input.trigger( "focus" );
27457		}
27458
27459		// Deffered render of the years select (to avoid flashes on Firefox)
27460		if ( inst.yearshtml ) {
27461			origyearshtml = inst.yearshtml;
27462			setTimeout( function() {
27463
27464				//assure that inst.yearshtml didn't change.
27465				if ( origyearshtml === inst.yearshtml && inst.yearshtml ) {
27466					inst.dpDiv.find( "select.ui-datepicker-year:first" ).replaceWith( inst.yearshtml );
27467				}
27468				origyearshtml = inst.yearshtml = null;
27469			}, 0 );
27470		}
27471	},
27472
27473	// #6694 - don't focus the input if it's already focused
27474	// this breaks the change event in IE
27475	// Support: IE and jQuery <1.9
27476	_shouldFocusInput: function( inst ) {
27477		return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" );
27478	},
27479
27480	/* Check positioning to remain on screen. */
27481	_checkOffset: function( inst, offset, isFixed ) {
27482		var dpWidth = inst.dpDiv.outerWidth(),
27483			dpHeight = inst.dpDiv.outerHeight(),
27484			inputWidth = inst.input ? inst.input.outerWidth() : 0,
27485			inputHeight = inst.input ? inst.input.outerHeight() : 0,
27486			viewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ),
27487			viewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() );
27488
27489		offset.left -= ( this._get( inst, "isRTL" ) ? ( dpWidth - inputWidth ) : 0 );
27490		offset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0;
27491		offset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0;
27492
27493		// Now check if datepicker is showing outside window viewport - move to a better place if so.
27494		offset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ?
27495			Math.abs( offset.left + dpWidth - viewWidth ) : 0 );
27496		offset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ?
27497			Math.abs( dpHeight + inputHeight ) : 0 );
27498
27499		return offset;
27500	},
27501
27502	/* Find an object's position on the screen. */
27503	_findPos: function( obj ) {
27504		var position,
27505			inst = this._getInst( obj ),
27506			isRTL = this._get( inst, "isRTL" );
27507
27508		while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) {
27509			obj = obj[ isRTL ? "previousSibling" : "nextSibling" ];
27510		}
27511
27512		position = $( obj ).offset();
27513		return [ position.left, position.top ];
27514	},
27515
27516	/* Hide the date picker from view.
27517	 * @param  input  element - the input field attached to the date picker
27518	 */
27519	_hideDatepicker: function( input ) {
27520		var showAnim, duration, postProcess, onClose,
27521			inst = this._curInst;
27522
27523		if ( !inst || ( input && inst !== $.data( input, "datepicker" ) ) ) {
27524			return;
27525		}
27526
27527		if ( this._datepickerShowing ) {
27528			showAnim = this._get( inst, "showAnim" );
27529			duration = this._get( inst, "duration" );
27530			postProcess = function() {
27531				$.datepicker._tidyDialog( inst );
27532			};
27533
27534			// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
27535			if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {
27536				inst.dpDiv.hide( showAnim, $.datepicker._get( inst, "showOptions" ), duration, postProcess );
27537			} else {
27538				inst.dpDiv[ ( showAnim === "slideDown" ? "slideUp" :
27539					( showAnim === "fadeIn" ? "fadeOut" : "hide" ) ) ]( ( showAnim ? duration : null ), postProcess );
27540			}
27541
27542			if ( !showAnim ) {
27543				postProcess();
27544			}
27545			this._datepickerShowing = false;
27546
27547			onClose = this._get( inst, "onClose" );
27548			if ( onClose ) {
27549				onClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : "" ), inst ] );
27550			}
27551
27552			this._lastInput = null;
27553			if ( this._inDialog ) {
27554				this._dialogInput.css( { position: "absolute", left: "0", top: "-100px" } );
27555				if ( $.blockUI ) {
27556					$.unblockUI();
27557					$( "body" ).append( this.dpDiv );
27558				}
27559			}
27560			this._inDialog = false;
27561		}
27562	},
27563
27564	/* Tidy up after a dialog display. */
27565	_tidyDialog: function( inst ) {
27566		inst.dpDiv.removeClass( this._dialogClass ).off( ".ui-datepicker-calendar" );
27567	},
27568
27569	/* Close date picker if clicked elsewhere. */
27570	_checkExternalClick: function( event ) {
27571		if ( !$.datepicker._curInst ) {
27572			return;
27573		}
27574
27575		var $target = $( event.target ),
27576			inst = $.datepicker._getInst( $target[ 0 ] );
27577
27578		if ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId &&
27579				$target.parents( "#" + $.datepicker._mainDivId ).length === 0 &&
27580				!$target.hasClass( $.datepicker.markerClassName ) &&
27581				!$target.closest( "." + $.datepicker._triggerClass ).length &&
27582				$.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) ||
27583			( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) {
27584				$.datepicker._hideDatepicker();
27585		}
27586	},
27587
27588	/* Adjust one of the date sub-fields. */
27589	_adjustDate: function( id, offset, period ) {
27590		var target = $( id ),
27591			inst = this._getInst( target[ 0 ] );
27592
27593		if ( this._isDisabledDatepicker( target[ 0 ] ) ) {
27594			return;
27595		}
27596		this._adjustInstDate( inst, offset +
27597			( period === "M" ? this._get( inst, "showCurrentAtPos" ) : 0 ), // undo positioning
27598			period );
27599		this._updateDatepicker( inst );
27600	},
27601
27602	/* Action for current link. */
27603	_gotoToday: function( id ) {
27604		var date,
27605			target = $( id ),
27606			inst = this._getInst( target[ 0 ] );
27607
27608		if ( this._get( inst, "gotoCurrent" ) && inst.currentDay ) {
27609			inst.selectedDay = inst.currentDay;
27610			inst.drawMonth = inst.selectedMonth = inst.currentMonth;
27611			inst.drawYear = inst.selectedYear = inst.currentYear;
27612		} else {
27613			date = new Date();
27614			inst.selectedDay = date.getDate();
27615			inst.drawMonth = inst.selectedMonth = date.getMonth();
27616			inst.drawYear = inst.selectedYear = date.getFullYear();
27617		}
27618		this._notifyChange( inst );
27619		this._adjustDate( target );
27620	},
27621
27622	/* Action for selecting a new month/year. */
27623	_selectMonthYear: function( id, select, period ) {
27624		var target = $( id ),
27625			inst = this._getInst( target[ 0 ] );
27626
27627		inst[ "selected" + ( period === "M" ? "Month" : "Year" ) ] =
27628		inst[ "draw" + ( period === "M" ? "Month" : "Year" ) ] =
27629			parseInt( select.options[ select.selectedIndex ].value, 10 );
27630
27631		this._notifyChange( inst );
27632		this._adjustDate( target );
27633	},
27634
27635	/* Action for selecting a day. */
27636	_selectDay: function( id, month, year, td ) {
27637		var inst,
27638			target = $( id );
27639
27640		if ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) {
27641			return;
27642		}
27643
27644		inst = this._getInst( target[ 0 ] );
27645		inst.selectedDay = inst.currentDay = $( "a", td ).html();
27646		inst.selectedMonth = inst.currentMonth = month;
27647		inst.selectedYear = inst.currentYear = year;
27648		this._selectDate( id, this._formatDate( inst,
27649			inst.currentDay, inst.currentMonth, inst.currentYear ) );
27650	},
27651
27652	/* Erase the input field and hide the date picker. */
27653	_clearDate: function( id ) {
27654		var target = $( id );
27655		this._selectDate( target, "" );
27656	},
27657
27658	/* Update the input field with the selected date. */
27659	_selectDate: function( id, dateStr ) {
27660		var onSelect,
27661			target = $( id ),
27662			inst = this._getInst( target[ 0 ] );
27663
27664		dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) );
27665		if ( inst.input ) {
27666			inst.input.val( dateStr );
27667		}
27668		this._updateAlternate( inst );
27669
27670		onSelect = this._get( inst, "onSelect" );
27671		if ( onSelect ) {
27672			onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );  // trigger custom callback
27673		} else if ( inst.input ) {
27674			inst.input.trigger( "change" ); // fire the change event
27675		}
27676
27677		if ( inst.inline ) {
27678			this._updateDatepicker( inst );
27679		} else {
27680			this._hideDatepicker();
27681			this._lastInput = inst.input[ 0 ];
27682			if ( typeof( inst.input[ 0 ] ) !== "object" ) {
27683				inst.input.trigger( "focus" ); // restore focus
27684			}
27685			this._lastInput = null;
27686		}
27687	},
27688
27689	/* Update any alternate field to synchronise with the main field. */
27690	_updateAlternate: function( inst ) {
27691		var altFormat, date, dateStr,
27692			altField = this._get( inst, "altField" );
27693
27694		if ( altField ) { // update alternate field too
27695			altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" );
27696			date = this._getDate( inst );
27697			dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) );
27698			$( altField ).val( dateStr );
27699		}
27700	},
27701
27702	/* Set as beforeShowDay function to prevent selection of weekends.
27703	 * @param  date  Date - the date to customise
27704	 * @return [boolean, string] - is this date selectable?, what is its CSS class?
27705	 */
27706	noWeekends: function( date ) {
27707		var day = date.getDay();
27708		return [ ( day > 0 && day < 6 ), "" ];
27709	},
27710
27711	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
27712	 * @param  date  Date - the date to get the week for
27713	 * @return  number - the number of the week within the year that contains this date
27714	 */
27715	iso8601Week: function( date ) {
27716		var time,
27717			checkDate = new Date( date.getTime() );
27718
27719		// Find Thursday of this week starting on Monday
27720		checkDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) );
27721
27722		time = checkDate.getTime();
27723		checkDate.setMonth( 0 ); // Compare with Jan 1
27724		checkDate.setDate( 1 );
27725		return Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1;
27726	},
27727
27728	/* Parse a string value into a date object.
27729	 * See formatDate below for the possible formats.
27730	 *
27731	 * @param  format string - the expected format of the date
27732	 * @param  value string - the date in the above format
27733	 * @param  settings Object - attributes include:
27734	 *					shortYearCutoff  number - the cutoff year for determining the century (optional)
27735	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
27736	 *					dayNames		string[7] - names of the days from Sunday (optional)
27737	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
27738	 *					monthNames		string[12] - names of the months (optional)
27739	 * @return  Date - the extracted date value or null if value is blank
27740	 */
27741	parseDate: function( format, value, settings ) {
27742		if ( format == null || value == null ) {
27743			throw "Invalid arguments";
27744		}
27745
27746		value = ( typeof value === "object" ? value.toString() : value + "" );
27747		if ( value === "" ) {
27748			return null;
27749		}
27750
27751		var iFormat, dim, extra,
27752			iValue = 0,
27753			shortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff,
27754			shortYearCutoff = ( typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp :
27755				new Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ),
27756			dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,
27757			dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,
27758			monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,
27759			monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,
27760			year = -1,
27761			month = -1,
27762			day = -1,
27763			doy = -1,
27764			literal = false,
27765			date,
27766
27767			// Check whether a format character is doubled
27768			lookAhead = function( match ) {
27769				var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );
27770				if ( matches ) {
27771					iFormat++;
27772				}
27773				return matches;
27774			},
27775
27776			// Extract a number from the string value
27777			getNumber = function( match ) {
27778				var isDoubled = lookAhead( match ),
27779					size = ( match === "@" ? 14 : ( match === "!" ? 20 :
27780					( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ),
27781					minSize = ( match === "y" ? size : 1 ),
27782					digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ),
27783					num = value.substring( iValue ).match( digits );
27784				if ( !num ) {
27785					throw "Missing number at position " + iValue;
27786				}
27787				iValue += num[ 0 ].length;
27788				return parseInt( num[ 0 ], 10 );
27789			},
27790
27791			// Extract a name from the string value and convert to an index
27792			getName = function( match, shortNames, longNames ) {
27793				var index = -1,
27794					names = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) {
27795						return [ [ k, v ] ];
27796					} ).sort( function( a, b ) {
27797						return -( a[ 1 ].length - b[ 1 ].length );
27798					} );
27799
27800				$.each( names, function( i, pair ) {
27801					var name = pair[ 1 ];
27802					if ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) {
27803						index = pair[ 0 ];
27804						iValue += name.length;
27805						return false;
27806					}
27807				} );
27808				if ( index !== -1 ) {
27809					return index + 1;
27810				} else {
27811					throw "Unknown name at position " + iValue;
27812				}
27813			},
27814
27815			// Confirm that a literal character matches the string value
27816			checkLiteral = function() {
27817				if ( value.charAt( iValue ) !== format.charAt( iFormat ) ) {
27818					throw "Unexpected literal at position " + iValue;
27819				}
27820				iValue++;
27821			};
27822
27823		for ( iFormat = 0; iFormat < format.length; iFormat++ ) {
27824			if ( literal ) {
27825				if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) {
27826					literal = false;
27827				} else {
27828					checkLiteral();
27829				}
27830			} else {
27831				switch ( format.charAt( iFormat ) ) {
27832					case "d":
27833						day = getNumber( "d" );
27834						break;
27835					case "D":
27836						getName( "D", dayNamesShort, dayNames );
27837						break;
27838					case "o":
27839						doy = getNumber( "o" );
27840						break;
27841					case "m":
27842						month = getNumber( "m" );
27843						break;
27844					case "M":
27845						month = getName( "M", monthNamesShort, monthNames );
27846						break;
27847					case "y":
27848						year = getNumber( "y" );
27849						break;
27850					case "@":
27851						date = new Date( getNumber( "@" ) );
27852						year = date.getFullYear();
27853						month = date.getMonth() + 1;
27854						day = date.getDate();
27855						break;
27856					case "!":
27857						date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 );
27858						year = date.getFullYear();
27859						month = date.getMonth() + 1;
27860						day = date.getDate();
27861						break;
27862					case "'":
27863						if ( lookAhead( "'" ) ) {
27864							checkLiteral();
27865						} else {
27866							literal = true;
27867						}
27868						break;
27869					default:
27870						checkLiteral();
27871				}
27872			}
27873		}
27874
27875		if ( iValue < value.length ) {
27876			extra = value.substr( iValue );
27877			if ( !/^\s+/.test( extra ) ) {
27878				throw "Extra/unparsed characters found in date: " + extra;
27879			}
27880		}
27881
27882		if ( year === -1 ) {
27883			year = new Date().getFullYear();
27884		} else if ( year < 100 ) {
27885			year += new Date().getFullYear() - new Date().getFullYear() % 100 +
27886				( year <= shortYearCutoff ? 0 : -100 );
27887		}
27888
27889		if ( doy > -1 ) {
27890			month = 1;
27891			day = doy;
27892			do {
27893				dim = this._getDaysInMonth( year, month - 1 );
27894				if ( day <= dim ) {
27895					break;
27896				}
27897				month++;
27898				day -= dim;
27899			} while ( true );
27900		}
27901
27902		date = this._daylightSavingAdjust( new Date( year, month - 1, day ) );
27903		if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) {
27904			throw "Invalid date"; // E.g. 31/02/00
27905		}
27906		return date;
27907	},
27908
27909	/* Standard date formats. */
27910	ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601)
27911	COOKIE: "D, dd M yy",
27912	ISO_8601: "yy-mm-dd",
27913	RFC_822: "D, d M y",
27914	RFC_850: "DD, dd-M-y",
27915	RFC_1036: "D, d M y",
27916	RFC_1123: "D, d M yy",
27917	RFC_2822: "D, d M yy",
27918	RSS: "D, d M y", // RFC 822
27919	TICKS: "!",
27920	TIMESTAMP: "@",
27921	W3C: "yy-mm-dd", // ISO 8601
27922
27923	_ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) +
27924		Math.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ),
27925
27926	/* Format a date object into a string value.
27927	 * The format can be combinations of the following:
27928	 * d  - day of month (no leading zero)
27929	 * dd - day of month (two digit)
27930	 * o  - day of year (no leading zeros)
27931	 * oo - day of year (three digit)
27932	 * D  - day name short
27933	 * DD - day name long
27934	 * m  - month of year (no leading zero)
27935	 * mm - month of year (two digit)
27936	 * M  - month name short
27937	 * MM - month name long
27938	 * y  - year (two digit)
27939	 * yy - year (four digit)
27940	 * @ - Unix timestamp (ms since 01/01/1970)
27941	 * ! - Windows ticks (100ns since 01/01/0001)
27942	 * "..." - literal text
27943	 * '' - single quote
27944	 *
27945	 * @param  format string - the desired format of the date
27946	 * @param  date Date - the date value to format
27947	 * @param  settings Object - attributes include:
27948	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
27949	 *					dayNames		string[7] - names of the days from Sunday (optional)
27950	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
27951	 *					monthNames		string[12] - names of the months (optional)
27952	 * @return  string - the date in the above format
27953	 */
27954	formatDate: function( format, date, settings ) {
27955		if ( !date ) {
27956			return "";
27957		}
27958
27959		var iFormat,
27960			dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,
27961			dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,
27962			monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,
27963			monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,
27964
27965			// Check whether a format character is doubled
27966			lookAhead = function( match ) {
27967				var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );
27968				if ( matches ) {
27969					iFormat++;
27970				}
27971				return matches;
27972			},
27973
27974			// Format a number, with leading zero if necessary
27975			formatNumber = function( match, value, len ) {
27976				var num = "" + value;
27977				if ( lookAhead( match ) ) {
27978					while ( num.length < len ) {
27979						num = "0" + num;
27980					}
27981				}
27982				return num;
27983			},
27984
27985			// Format a name, short or long as requested
27986			formatName = function( match, value, shortNames, longNames ) {
27987				return ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] );
27988			},
27989			output = "",
27990			literal = false;
27991
27992		if ( date ) {
27993			for ( iFormat = 0; iFormat < format.length; iFormat++ ) {
27994				if ( literal ) {
27995					if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) {
27996						literal = false;
27997					} else {
27998						output += format.charAt( iFormat );
27999					}
28000				} else {
28001					switch ( format.charAt( iFormat ) ) {
28002						case "d":
28003							output += formatNumber( "d", date.getDate(), 2 );
28004							break;
28005						case "D":
28006							output += formatName( "D", date.getDay(), dayNamesShort, dayNames );
28007							break;
28008						case "o":
28009							output += formatNumber( "o",
28010								Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 );
28011							break;
28012						case "m":
28013							output += formatNumber( "m", date.getMonth() + 1, 2 );
28014							break;
28015						case "M":
28016							output += formatName( "M", date.getMonth(), monthNamesShort, monthNames );
28017							break;
28018						case "y":
28019							output += ( lookAhead( "y" ) ? date.getFullYear() :
28020								( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 );
28021							break;
28022						case "@":
28023							output += date.getTime();
28024							break;
28025						case "!":
28026							output += date.getTime() * 10000 + this._ticksTo1970;
28027							break;
28028						case "'":
28029							if ( lookAhead( "'" ) ) {
28030								output += "'";
28031							} else {
28032								literal = true;
28033							}
28034							break;
28035						default:
28036							output += format.charAt( iFormat );
28037					}
28038				}
28039			}
28040		}
28041		return output;
28042	},
28043
28044	/* Extract all possible characters from the date format. */
28045	_possibleChars: function( format ) {
28046		var iFormat,
28047			chars = "",
28048			literal = false,
28049
28050			// Check whether a format character is doubled
28051			lookAhead = function( match ) {
28052				var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );
28053				if ( matches ) {
28054					iFormat++;
28055				}
28056				return matches;
28057			};
28058
28059		for ( iFormat = 0; iFormat < format.length; iFormat++ ) {
28060			if ( literal ) {
28061				if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) {
28062					literal = false;
28063				} else {
28064					chars += format.charAt( iFormat );
28065				}
28066			} else {
28067				switch ( format.charAt( iFormat ) ) {
28068					case "d": case "m": case "y": case "@":
28069						chars += "0123456789";
28070						break;
28071					case "D": case "M":
28072						return null; // Accept anything
28073					case "'":
28074						if ( lookAhead( "'" ) ) {
28075							chars += "'";
28076						} else {
28077							literal = true;
28078						}
28079						break;
28080					default:
28081						chars += format.charAt( iFormat );
28082				}
28083			}
28084		}
28085		return chars;
28086	},
28087
28088	/* Get a setting value, defaulting if necessary. */
28089	_get: function( inst, name ) {
28090		return inst.settings[ name ] !== undefined ?
28091			inst.settings[ name ] : this._defaults[ name ];
28092	},
28093
28094	/* Parse existing date and initialise date picker. */
28095	_setDateFromField: function( inst, noDefault ) {
28096		if ( inst.input.val() === inst.lastVal ) {
28097			return;
28098		}
28099
28100		var dateFormat = this._get( inst, "dateFormat" ),
28101			dates = inst.lastVal = inst.input ? inst.input.val() : null,
28102			defaultDate = this._getDefaultDate( inst ),
28103			date = defaultDate,
28104			settings = this._getFormatConfig( inst );
28105
28106		try {
28107			date = this.parseDate( dateFormat, dates, settings ) || defaultDate;
28108		} catch ( event ) {
28109			dates = ( noDefault ? "" : dates );
28110		}
28111		inst.selectedDay = date.getDate();
28112		inst.drawMonth = inst.selectedMonth = date.getMonth();
28113		inst.drawYear = inst.selectedYear = date.getFullYear();
28114		inst.currentDay = ( dates ? date.getDate() : 0 );
28115		inst.currentMonth = ( dates ? date.getMonth() : 0 );
28116		inst.currentYear = ( dates ? date.getFullYear() : 0 );
28117		this._adjustInstDate( inst );
28118	},
28119
28120	/* Retrieve the default date shown on opening. */
28121	_getDefaultDate: function( inst ) {
28122		return this._restrictMinMax( inst,
28123			this._determineDate( inst, this._get( inst, "defaultDate" ), new Date() ) );
28124	},
28125
28126	/* A date may be specified as an exact value or a relative one. */
28127	_determineDate: function( inst, date, defaultDate ) {
28128		var offsetNumeric = function( offset ) {
28129				var date = new Date();
28130				date.setDate( date.getDate() + offset );
28131				return date;
28132			},
28133			offsetString = function( offset ) {
28134				try {
28135					return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ),
28136						offset, $.datepicker._getFormatConfig( inst ) );
28137				}
28138				catch ( e ) {
28139
28140					// Ignore
28141				}
28142
28143				var date = ( offset.toLowerCase().match( /^c/ ) ?
28144					$.datepicker._getDate( inst ) : null ) || new Date(),
28145					year = date.getFullYear(),
28146					month = date.getMonth(),
28147					day = date.getDate(),
28148					pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
28149					matches = pattern.exec( offset );
28150
28151				while ( matches ) {
28152					switch ( matches[ 2 ] || "d" ) {
28153						case "d" : case "D" :
28154							day += parseInt( matches[ 1 ], 10 ); break;
28155						case "w" : case "W" :
28156							day += parseInt( matches[ 1 ], 10 ) * 7; break;
28157						case "m" : case "M" :
28158							month += parseInt( matches[ 1 ], 10 );
28159							day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );
28160							break;
28161						case "y": case "Y" :
28162							year += parseInt( matches[ 1 ], 10 );
28163							day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );
28164							break;
28165					}
28166					matches = pattern.exec( offset );
28167				}
28168				return new Date( year, month, day );
28169			},
28170			newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) :
28171				( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) );
28172
28173		newDate = ( newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate );
28174		if ( newDate ) {
28175			newDate.setHours( 0 );
28176			newDate.setMinutes( 0 );
28177			newDate.setSeconds( 0 );
28178			newDate.setMilliseconds( 0 );
28179		}
28180		return this._daylightSavingAdjust( newDate );
28181	},
28182
28183	/* Handle switch to/from daylight saving.
28184	 * Hours may be non-zero on daylight saving cut-over:
28185	 * > 12 when midnight changeover, but then cannot generate
28186	 * midnight datetime, so jump to 1AM, otherwise reset.
28187	 * @param  date  (Date) the date to check
28188	 * @return  (Date) the corrected date
28189	 */
28190	_daylightSavingAdjust: function( date ) {
28191		if ( !date ) {
28192			return null;
28193		}
28194		date.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 );
28195		return date;
28196	},
28197
28198	/* Set the date(s) directly. */
28199	_setDate: function( inst, date, noChange ) {
28200		var clear = !date,
28201			origMonth = inst.selectedMonth,
28202			origYear = inst.selectedYear,
28203			newDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) );
28204
28205		inst.selectedDay = inst.currentDay = newDate.getDate();
28206		inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
28207		inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
28208		if ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) {
28209			this._notifyChange( inst );
28210		}
28211		this._adjustInstDate( inst );
28212		if ( inst.input ) {
28213			inst.input.val( clear ? "" : this._formatDate( inst ) );
28214		}
28215	},
28216
28217	/* Retrieve the date(s) directly. */
28218	_getDate: function( inst ) {
28219		var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null :
28220			this._daylightSavingAdjust( new Date(
28221			inst.currentYear, inst.currentMonth, inst.currentDay ) ) );
28222			return startDate;
28223	},
28224
28225	/* Attach the onxxx handlers.  These are declared statically so
28226	 * they work with static code transformers like Caja.
28227	 */
28228	_attachHandlers: function( inst ) {
28229		var stepMonths = this._get( inst, "stepMonths" ),
28230			id = "#" + inst.id.replace( /\\\\/g, "\\" );
28231		inst.dpDiv.find( "[data-handler]" ).map( function() {
28232			var handler = {
28233				prev: function() {
28234					$.datepicker._adjustDate( id, -stepMonths, "M" );
28235				},
28236				next: function() {
28237					$.datepicker._adjustDate( id, +stepMonths, "M" );
28238				},
28239				hide: function() {
28240					$.datepicker._hideDatepicker();
28241				},
28242				today: function() {
28243					$.datepicker._gotoToday( id );
28244				},
28245				selectDay: function() {
28246					$.datepicker._selectDay( id, +this.getAttribute( "data-month" ), +this.getAttribute( "data-year" ), this );
28247					return false;
28248				},
28249				selectMonth: function() {
28250					$.datepicker._selectMonthYear( id, this, "M" );
28251					return false;
28252				},
28253				selectYear: function() {
28254					$.datepicker._selectMonthYear( id, this, "Y" );
28255					return false;
28256				}
28257			};
28258			$( this ).on( this.getAttribute( "data-event" ), handler[ this.getAttribute( "data-handler" ) ] );
28259		} );
28260	},
28261
28262	/* Generate the HTML for the current state of the date picker. */
28263	_generateHTML: function( inst ) {
28264		var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
28265			controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
28266			monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
28267			selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
28268			cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
28269			printDate, dRow, tbody, daySettings, otherMonth, unselectable,
28270			tempDate = new Date(),
28271			today = this._daylightSavingAdjust(
28272				new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time
28273			isRTL = this._get( inst, "isRTL" ),
28274			showButtonPanel = this._get( inst, "showButtonPanel" ),
28275			hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ),
28276			navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ),
28277			numMonths = this._getNumberOfMonths( inst ),
28278			showCurrentAtPos = this._get( inst, "showCurrentAtPos" ),
28279			stepMonths = this._get( inst, "stepMonths" ),
28280			isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ),
28281			currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) :
28282				new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ),
28283			minDate = this._getMinMaxDate( inst, "min" ),
28284			maxDate = this._getMinMaxDate( inst, "max" ),
28285			drawMonth = inst.drawMonth - showCurrentAtPos,
28286			drawYear = inst.drawYear;
28287
28288		if ( drawMonth < 0 ) {
28289			drawMonth += 12;
28290			drawYear--;
28291		}
28292		if ( maxDate ) {
28293			maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(),
28294				maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) );
28295			maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw );
28296			while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) {
28297				drawMonth--;
28298				if ( drawMonth < 0 ) {
28299					drawMonth = 11;
28300					drawYear--;
28301				}
28302			}
28303		}
28304		inst.drawMonth = drawMonth;
28305		inst.drawYear = drawYear;
28306
28307		prevText = this._get( inst, "prevText" );
28308		prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText,
28309			this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ),
28310			this._getFormatConfig( inst ) ) );
28311
28312		prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ?
28313			"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
28314			" title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" :
28315			( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" ) );
28316
28317		nextText = this._get( inst, "nextText" );
28318		nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText,
28319			this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ),
28320			this._getFormatConfig( inst ) ) );
28321
28322		next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ?
28323			"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
28324			" title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" :
28325			( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" ) );
28326
28327		currentText = this._get( inst, "currentText" );
28328		gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today );
28329		currentText = ( !navigationAsDateFormat ? currentText :
28330			this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) );
28331
28332		controls = ( !inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
28333			this._get( inst, "closeText" ) + "</button>" : "" );
28334
28335		buttonPanel = ( showButtonPanel ) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + ( isRTL ? controls : "" ) +
28336			( this._isInRange( inst, gotoDate ) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
28337			">" + currentText + "</button>" : "" ) + ( isRTL ? "" : controls ) + "</div>" : "";
28338
28339		firstDay = parseInt( this._get( inst, "firstDay" ), 10 );
28340		firstDay = ( isNaN( firstDay ) ? 0 : firstDay );
28341
28342		showWeek = this._get( inst, "showWeek" );
28343		dayNames = this._get( inst, "dayNames" );
28344		dayNamesMin = this._get( inst, "dayNamesMin" );
28345		monthNames = this._get( inst, "monthNames" );
28346		monthNamesShort = this._get( inst, "monthNamesShort" );
28347		beforeShowDay = this._get( inst, "beforeShowDay" );
28348		showOtherMonths = this._get( inst, "showOtherMonths" );
28349		selectOtherMonths = this._get( inst, "selectOtherMonths" );
28350		defaultDate = this._getDefaultDate( inst );
28351		html = "";
28352
28353		for ( row = 0; row < numMonths[ 0 ]; row++ ) {
28354			group = "";
28355			this.maxRows = 4;
28356			for ( col = 0; col < numMonths[ 1 ]; col++ ) {
28357				selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) );
28358				cornerClass = " ui-corner-all";
28359				calender = "";
28360				if ( isMultiMonth ) {
28361					calender += "<div class='ui-datepicker-group";
28362					if ( numMonths[ 1 ] > 1 ) {
28363						switch ( col ) {
28364							case 0: calender += " ui-datepicker-group-first";
28365								cornerClass = " ui-corner-" + ( isRTL ? "right" : "left" ); break;
28366							case numMonths[ 1 ] - 1: calender += " ui-datepicker-group-last";
28367								cornerClass = " ui-corner-" + ( isRTL ? "left" : "right" ); break;
28368							default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break;
28369						}
28370					}
28371					calender += "'>";
28372				}
28373				calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" +
28374					( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) +
28375					( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) +
28376					this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate,
28377					row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers
28378					"</div><table class='ui-datepicker-calendar'><thead>" +
28379					"<tr>";
28380				thead = ( showWeek ? "<th class='ui-datepicker-week-col'>" + this._get( inst, "weekHeader" ) + "</th>" : "" );
28381				for ( dow = 0; dow < 7; dow++ ) { // days of the week
28382					day = ( dow + firstDay ) % 7;
28383					thead += "<th scope='col'" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" +
28384						"<span title='" + dayNames[ day ] + "'>" + dayNamesMin[ day ] + "</span></th>";
28385				}
28386				calender += thead + "</tr></thead><tbody>";
28387				daysInMonth = this._getDaysInMonth( drawYear, drawMonth );
28388				if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) {
28389					inst.selectedDay = Math.min( inst.selectedDay, daysInMonth );
28390				}
28391				leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7;
28392				curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate
28393				numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043)
28394				this.maxRows = numRows;
28395				printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) );
28396				for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows
28397					calender += "<tr>";
28398					tbody = ( !showWeek ? "" : "<td class='ui-datepicker-week-col'>" +
28399						this._get( inst, "calculateWeek" )( printDate ) + "</td>" );
28400					for ( dow = 0; dow < 7; dow++ ) { // create date picker days
28401						daySettings = ( beforeShowDay ?
28402							beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] );
28403						otherMonth = ( printDate.getMonth() !== drawMonth );
28404						unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] ||
28405							( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate );
28406						tbody += "<td class='" +
28407							( ( dow + firstDay + 6 ) % 7 >= 5 ? " ui-datepicker-week-end" : "" ) + // highlight weekends
28408							( otherMonth ? " ui-datepicker-other-month" : "" ) + // highlight days from other months
28409							( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key
28410							( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ?
28411
28412							// or defaultDate is current printedDate and defaultDate is selectedDate
28413							" " + this._dayOverClass : "" ) + // highlight selected day
28414							( unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "" ) +  // highlight unselectable days
28415							( otherMonth && !showOtherMonths ? "" : " " + daySettings[ 1 ] + // highlight custom dates
28416							( printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "" ) + // highlight selected day
28417							( printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "" ) ) + "'" + // highlight today (if different)
28418							( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? " title='" + daySettings[ 2 ].replace( /'/g, "&#39;" ) + "'" : "" ) + // cell title
28419							( unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'" ) + ">" + // actions
28420							( otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
28421							( unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
28422							( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) +
28423							( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day
28424							( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months
28425							"' href='#'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date
28426						printDate.setDate( printDate.getDate() + 1 );
28427						printDate = this._daylightSavingAdjust( printDate );
28428					}
28429					calender += tbody + "</tr>";
28430				}
28431				drawMonth++;
28432				if ( drawMonth > 11 ) {
28433					drawMonth = 0;
28434					drawYear++;
28435				}
28436				calender += "</tbody></table>" + ( isMultiMonth ? "</div>" +
28437							( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "<div class='ui-datepicker-row-break'></div>" : "" ) : "" );
28438				group += calender;
28439			}
28440			html += group;
28441		}
28442		html += buttonPanel;
28443		inst._keyEvent = false;
28444		return html;
28445	},
28446
28447	/* Generate the month and year header. */
28448	_generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate,
28449			secondary, monthNames, monthNamesShort ) {
28450
28451		var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
28452			changeMonth = this._get( inst, "changeMonth" ),
28453			changeYear = this._get( inst, "changeYear" ),
28454			showMonthAfterYear = this._get( inst, "showMonthAfterYear" ),
28455			html = "<div class='ui-datepicker-title'>",
28456			monthHtml = "";
28457
28458		// Month selection
28459		if ( secondary || !changeMonth ) {
28460			monthHtml += "<span class='ui-datepicker-month'>" + monthNames[ drawMonth ] + "</span>";
28461		} else {
28462			inMinYear = ( minDate && minDate.getFullYear() === drawYear );
28463			inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear );
28464			monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>";
28465			for ( month = 0; month < 12; month++ ) {
28466				if ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) {
28467					monthHtml += "<option value='" + month + "'" +
28468						( month === drawMonth ? " selected='selected'" : "" ) +
28469						">" + monthNamesShort[ month ] + "</option>";
28470				}
28471			}
28472			monthHtml += "</select>";
28473		}
28474
28475		if ( !showMonthAfterYear ) {
28476			html += monthHtml + ( secondary || !( changeMonth && changeYear ) ? "&#xa0;" : "" );
28477		}
28478
28479		// Year selection
28480		if ( !inst.yearshtml ) {
28481			inst.yearshtml = "";
28482			if ( secondary || !changeYear ) {
28483				html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
28484			} else {
28485
28486				// determine range of years to display
28487				years = this._get( inst, "yearRange" ).split( ":" );
28488				thisYear = new Date().getFullYear();
28489				determineYear = function( value ) {
28490					var year = ( value.match( /c[+\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) :
28491						( value.match( /[+\-].*/ ) ? thisYear + parseInt( value, 10 ) :
28492						parseInt( value, 10 ) ) );
28493					return ( isNaN( year ) ? thisYear : year );
28494				};
28495				year = determineYear( years[ 0 ] );
28496				endYear = Math.max( year, determineYear( years[ 1 ] || "" ) );
28497				year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year );
28498				endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear );
28499				inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";
28500				for ( ; year <= endYear; year++ ) {
28501					inst.yearshtml += "<option value='" + year + "'" +
28502						( year === drawYear ? " selected='selected'" : "" ) +
28503						">" + year + "</option>";
28504				}
28505				inst.yearshtml += "</select>";
28506
28507				html += inst.yearshtml;
28508				inst.yearshtml = null;
28509			}
28510		}
28511
28512		html += this._get( inst, "yearSuffix" );
28513		if ( showMonthAfterYear ) {
28514			html += ( secondary || !( changeMonth && changeYear ) ? "&#xa0;" : "" ) + monthHtml;
28515		}
28516		html += "</div>"; // Close datepicker_header
28517		return html;
28518	},
28519
28520	/* Adjust one of the date sub-fields. */
28521	_adjustInstDate: function( inst, offset, period ) {
28522		var year = inst.selectedYear + ( period === "Y" ? offset : 0 ),
28523			month = inst.selectedMonth + ( period === "M" ? offset : 0 ),
28524			day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ),
28525			date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) );
28526
28527		inst.selectedDay = date.getDate();
28528		inst.drawMonth = inst.selectedMonth = date.getMonth();
28529		inst.drawYear = inst.selectedYear = date.getFullYear();
28530		if ( period === "M" || period === "Y" ) {
28531			this._notifyChange( inst );
28532		}
28533	},
28534
28535	/* Ensure a date is within any min/max bounds. */
28536	_restrictMinMax: function( inst, date ) {
28537		var minDate = this._getMinMaxDate( inst, "min" ),
28538			maxDate = this._getMinMaxDate( inst, "max" ),
28539			newDate = ( minDate && date < minDate ? minDate : date );
28540		return ( maxDate && newDate > maxDate ? maxDate : newDate );
28541	},
28542
28543	/* Notify change of month/year. */
28544	_notifyChange: function( inst ) {
28545		var onChange = this._get( inst, "onChangeMonthYear" );
28546		if ( onChange ) {
28547			onChange.apply( ( inst.input ? inst.input[ 0 ] : null ),
28548				[ inst.selectedYear, inst.selectedMonth + 1, inst ] );
28549		}
28550	},
28551
28552	/* Determine the number of months to show. */
28553	_getNumberOfMonths: function( inst ) {
28554		var numMonths = this._get( inst, "numberOfMonths" );
28555		return ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === "number" ? [ 1, numMonths ] : numMonths ) );
28556	},
28557
28558	/* Determine the current maximum date - ensure no time components are set. */
28559	_getMinMaxDate: function( inst, minMax ) {
28560		return this._determineDate( inst, this._get( inst, minMax + "Date" ), null );
28561	},
28562
28563	/* Find the number of days in a given month. */
28564	_getDaysInMonth: function( year, month ) {
28565		return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate();
28566	},
28567
28568	/* Find the day of the week of the first of a month. */
28569	_getFirstDayOfMonth: function( year, month ) {
28570		return new Date( year, month, 1 ).getDay();
28571	},
28572
28573	/* Determines if we should allow a "next/prev" month display change. */
28574	_canAdjustMonth: function( inst, offset, curYear, curMonth ) {
28575		var numMonths = this._getNumberOfMonths( inst ),
28576			date = this._daylightSavingAdjust( new Date( curYear,
28577			curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) );
28578
28579		if ( offset < 0 ) {
28580			date.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) );
28581		}
28582		return this._isInRange( inst, date );
28583	},
28584
28585	/* Is the given date in the accepted range? */
28586	_isInRange: function( inst, date ) {
28587		var yearSplit, currentYear,
28588			minDate = this._getMinMaxDate( inst, "min" ),
28589			maxDate = this._getMinMaxDate( inst, "max" ),
28590			minYear = null,
28591			maxYear = null,
28592			years = this._get( inst, "yearRange" );
28593			if ( years ) {
28594				yearSplit = years.split( ":" );
28595				currentYear = new Date().getFullYear();
28596				minYear = parseInt( yearSplit[ 0 ], 10 );
28597				maxYear = parseInt( yearSplit[ 1 ], 10 );
28598				if ( yearSplit[ 0 ].match( /[+\-].*/ ) ) {
28599					minYear += currentYear;
28600				}
28601				if ( yearSplit[ 1 ].match( /[+\-].*/ ) ) {
28602					maxYear += currentYear;
28603				}
28604			}
28605
28606		return ( ( !minDate || date.getTime() >= minDate.getTime() ) &&
28607			( !maxDate || date.getTime() <= maxDate.getTime() ) &&
28608			( !minYear || date.getFullYear() >= minYear ) &&
28609			( !maxYear || date.getFullYear() <= maxYear ) );
28610	},
28611
28612	/* Provide the configuration settings for formatting/parsing. */
28613	_getFormatConfig: function( inst ) {
28614		var shortYearCutoff = this._get( inst, "shortYearCutoff" );
28615		shortYearCutoff = ( typeof shortYearCutoff !== "string" ? shortYearCutoff :
28616			new Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) );
28617		return { shortYearCutoff: shortYearCutoff,
28618			dayNamesShort: this._get( inst, "dayNamesShort" ), dayNames: this._get( inst, "dayNames" ),
28619			monthNamesShort: this._get( inst, "monthNamesShort" ), monthNames: this._get( inst, "monthNames" ) };
28620	},
28621
28622	/* Format the given date for display. */
28623	_formatDate: function( inst, day, month, year ) {
28624		if ( !day ) {
28625			inst.currentDay = inst.selectedDay;
28626			inst.currentMonth = inst.selectedMonth;
28627			inst.currentYear = inst.selectedYear;
28628		}
28629		var date = ( day ? ( typeof day === "object" ? day :
28630			this._daylightSavingAdjust( new Date( year, month, day ) ) ) :
28631			this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) );
28632		return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) );
28633	}
28634} );
28635
28636/*
28637 * Bind hover events for datepicker elements.
28638 * Done via delegate so the binding only occurs once in the lifetime of the parent div.
28639 * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
28640 */
28641function datepicker_bindHover( dpDiv ) {
28642	var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";
28643	return dpDiv.on( "mouseout", selector, function() {
28644			$( this ).removeClass( "ui-state-hover" );
28645			if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) {
28646				$( this ).removeClass( "ui-datepicker-prev-hover" );
28647			}
28648			if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) {
28649				$( this ).removeClass( "ui-datepicker-next-hover" );
28650			}
28651		} )
28652		.on( "mouseover", selector, datepicker_handleMouseover );
28653}
28654
28655function datepicker_handleMouseover() {
28656	if ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) {
28657		$( this ).parents( ".ui-datepicker-calendar" ).find( "a" ).removeClass( "ui-state-hover" );
28658		$( this ).addClass( "ui-state-hover" );
28659		if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) {
28660			$( this ).addClass( "ui-datepicker-prev-hover" );
28661		}
28662		if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) {
28663			$( this ).addClass( "ui-datepicker-next-hover" );
28664		}
28665	}
28666}
28667
28668/* jQuery extend now ignores nulls! */
28669function datepicker_extendRemove( target, props ) {
28670	$.extend( target, props );
28671	for ( var name in props ) {
28672		if ( props[ name ] == null ) {
28673			target[ name ] = props[ name ];
28674		}
28675	}
28676	return target;
28677}
28678
28679/* Invoke the datepicker functionality.
28680   @param  options  string - a command, optionally followed by additional parameters or
28681					Object - settings for attaching new datepicker functionality
28682   @return  jQuery object */
28683$.fn.datepicker = function( options ) {
28684
28685	/* Verify an empty collection wasn't passed - Fixes #6976 */
28686	if ( !this.length ) {
28687		return this;
28688	}
28689
28690	/* Initialise the date picker. */
28691	if ( !$.datepicker.initialized ) {
28692		$( document ).on( "mousedown", $.datepicker._checkExternalClick );
28693		$.datepicker.initialized = true;
28694	}
28695
28696	/* Append datepicker main container to body if not exist. */
28697	if ( $( "#" + $.datepicker._mainDivId ).length === 0 ) {
28698		$( "body" ).append( $.datepicker.dpDiv );
28699	}
28700
28701	var otherArgs = Array.prototype.slice.call( arguments, 1 );
28702	if ( typeof options === "string" && ( options === "isDisabled" || options === "getDate" || options === "widget" ) ) {
28703		return $.datepicker[ "_" + options + "Datepicker" ].
28704			apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );
28705	}
28706	if ( options === "option" && arguments.length === 2 && typeof arguments[ 1 ] === "string" ) {
28707		return $.datepicker[ "_" + options + "Datepicker" ].
28708			apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );
28709	}
28710	return this.each( function() {
28711		typeof options === "string" ?
28712			$.datepicker[ "_" + options + "Datepicker" ].
28713				apply( $.datepicker, [ this ].concat( otherArgs ) ) :
28714			$.datepicker._attachDatepicker( this, options );
28715	} );
28716};
28717
28718$.datepicker = new Datepicker(); // singleton instance
28719$.datepicker.initialized = false;
28720$.datepicker.uuid = new Date().getTime();
28721$.datepicker.version = "1.12.1";
28722
vendor: 7,012 bytes, lines 28723-29001
28723return $.datepicker;
28724
28725} ) );
28726
28727
28728
28729
28730/*!
28731 * jQuery UI Mouse 1.12.1
28732 * http://jqueryui.com
28733 *
28734 * Copyright jQuery Foundation and other contributors
28735 * Released under the MIT license.
28736 * http://jquery.org/license
28737 */
28738
28739//>>label: Mouse
28740//>>group: Widgets
28741//>>description: Abstracts mouse-based interactions to assist in creating certain widgets.
28742//>>docs: http://api.jqueryui.com/mouse/
28743
28744( function( factory ) {
28745	if ( typeof define === "function" && define.amd ) {
28746
28747		// AMD. Register as an anonymous module.
28748		define( [
28749			"jquery",
28750			"../ie",
28751			"../version",
28752			"../widget"
28753		], factory );
28754	} else {
28755
28756		// Browser globals
28757		factory( jQuery );
28758	}
28759}( function( $ ) {
28760
28761var mouseHandled = false;
28762$( document ).on( "mouseup", function() {
28763	mouseHandled = false;
28764} );
28765
28766return $.widget( "ui.mouse", {
28767	version: "1.12.1",
28768	options: {
28769		cancel: "input, textarea, button, select, option",
28770		distance: 1,
28771		delay: 0
28772	},
28773	_mouseInit: function() {
28774		var that = this;
28775
28776		this.element
28777			.on( "mousedown." + this.widgetName, function( event ) {
28778				return that._mouseDown( event );
28779			} )
28780			.on( "click." + this.widgetName, function( event ) {
28781				if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) {
28782					$.removeData( event.target, that.widgetName + ".preventClickEvent" );
28783					event.stopImmediatePropagation();
28784					return false;
28785				}
28786			} );
28787
28788		this.started = false;
28789	},
28790
28791	// TODO: make sure destroying one instance of mouse doesn't mess with
28792	// other instances of mouse
28793	_mouseDestroy: function() {
28794		this.element.off( "." + this.widgetName );
28795		if ( this._mouseMoveDelegate ) {
28796			this.document
28797				.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
28798				.off( "mouseup." + this.widgetName, this._mouseUpDelegate );
28799		}
28800	},
28801
28802	_mouseDown: function( event ) {
28803
28804		// don't let more than one widget handle mouseStart
28805		if ( mouseHandled ) {
28806			return;
28807		}
28808
28809		this._mouseMoved = false;
28810
28811		// We may have missed mouseup (out of window)
28812		( this._mouseStarted && this._mouseUp( event ) );
28813
28814		this._mouseDownEvent = event;
28815
28816		var that = this,
28817			btnIsLeft = ( event.which === 1 ),
28818
28819			// event.target.nodeName works around a bug in IE 8 with
28820			// disabled inputs (#7620)
28821			elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ?
28822				$( event.target ).closest( this.options.cancel ).length : false );
28823		if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) {
28824			return true;
28825		}
28826
28827		this.mouseDelayMet = !this.options.delay;
28828		if ( !this.mouseDelayMet ) {
28829			this._mouseDelayTimer = setTimeout( function() {
28830				that.mouseDelayMet = true;
28831			}, this.options.delay );
28832		}
28833
28834		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
28835			this._mouseStarted = ( this._mouseStart( event ) !== false );
28836			if ( !this._mouseStarted ) {
28837				event.preventDefault();
28838				return true;
28839			}
28840		}
28841
28842		// Click event may never have fired (Gecko & Opera)
28843		if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) {
28844			$.removeData( event.target, this.widgetName + ".preventClickEvent" );
28845		}
28846
28847		// These delegates are required to keep context
28848		this._mouseMoveDelegate = function( event ) {
28849			return that._mouseMove( event );
28850		};
28851		this._mouseUpDelegate = function( event ) {
28852			return that._mouseUp( event );
28853		};
28854
28855		this.document
28856			.on( "mousemove." + this.widgetName, this._mouseMoveDelegate )
28857			.on( "mouseup." + this.widgetName, this._mouseUpDelegate );
28858
28859		event.preventDefault();
28860
28861		mouseHandled = true;
28862		return true;
28863	},
28864
28865	_mouseMove: function( event ) {
28866
28867		// Only check for mouseups outside the document if you've moved inside the document
28868		// at least once. This prevents the firing of mouseup in the case of IE<9, which will
28869		// fire a mousemove event if content is placed under the cursor. See #7778
28870		// Support: IE <9
28871		if ( this._mouseMoved ) {
28872
28873			// IE mouseup check - mouseup happened when mouse was out of window
28874			if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) &&
28875					!event.button ) {
28876				return this._mouseUp( event );
28877
28878			// Iframe mouseup check - mouseup occurred in another document
28879			} else if ( !event.which ) {
28880
28881				// Support: Safari <=8 - 9
28882				// Safari sets which to 0 if you press any of the following keys
28883				// during a drag (#14461)
28884				if ( event.originalEvent.altKey || event.originalEvent.ctrlKey ||
28885						event.originalEvent.metaKey || event.originalEvent.shiftKey ) {
28886					this.ignoreMissingWhich = true;
28887				} else if ( !this.ignoreMissingWhich ) {
28888					return this._mouseUp( event );
28889				}
28890			}
28891		}
28892
28893		if ( event.which || event.button ) {
28894			this._mouseMoved = true;
28895		}
28896
28897		if ( this._mouseStarted ) {
28898			this._mouseDrag( event );
28899			return event.preventDefault();
28900		}
28901
28902		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
28903			this._mouseStarted =
28904				( this._mouseStart( this._mouseDownEvent, event ) !== false );
28905			( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) );
28906		}
28907
28908		return !this._mouseStarted;
28909	},
28910
28911	_mouseUp: function( event ) {
28912		this.document
28913			.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
28914			.off( "mouseup." + this.widgetName, this._mouseUpDelegate );
28915
28916		if ( this._mouseStarted ) {
28917			this._mouseStarted = false;
28918
28919			if ( event.target === this._mouseDownEvent.target ) {
28920				$.data( event.target, this.widgetName + ".preventClickEvent", true );
28921			}
28922
28923			this._mouseStop( event );
28924		}
28925
28926		if ( this._mouseDelayTimer ) {
28927			clearTimeout( this._mouseDelayTimer );
28928			delete this._mouseDelayTimer;
28929		}
28930
28931		this.ignoreMissingWhich = false;
28932		mouseHandled = false;
28933		event.preventDefault();
28934	},
28935
28936	_mouseDistanceMet: function( event ) {
28937		return ( Math.max(
28938				Math.abs( this._mouseDownEvent.pageX - event.pageX ),
28939				Math.abs( this._mouseDownEvent.pageY - event.pageY )
28940			) >= this.options.distance
28941		);
28942	},
28943
28944	_mouseDelayMet: function( /* event */ ) {
28945		return this.mouseDelayMet;
28946	},
28947
28948	// These are placeholder methods, to be overriden by extending plugin
28949	_mouseStart: function( /* event */ ) {},
28950	_mouseDrag: function( /* event */ ) {},
28951	_mouseStop: function( /* event */ ) {},
28952	_mouseCapture: function( /* event */ ) { return true; }
28953} );
28954
28955} ) );
28956
28957
28958
28959
28960
28961
28962
28963
28964
28965/*!
28966 * jQuery UI Draggable 1.12.1
28967 * http://jqueryui.com
28968 *
28969 * Copyright jQuery Foundation and other contributors
28970 * Released under the MIT license.
28971 * http://jquery.org/license
28972 */
28973
28974//>>label: Draggable
28975//>>group: Interactions
28976//>>description: Enables dragging functionality for any element.
28977//>>docs: http://api.jqueryui.com/draggable/
28978//>>demos: http://jqueryui.com/draggable/
28979//>>css.structure: ../../themes/base/draggable.css
28980
28981( function( factory ) {
28982	if ( typeof define === "function" && define.amd ) {
28983
28984		// AMD. Register as an anonymous module.
28985		define( [
28986			"jquery",
28987			"./mouse",
28988			"../data",
28989			"../plugin",
28990			"../safe-active-element",
28991			"../safe-blur",
28992			"../scroll-parent",
28993			"../version",
28994			"../widget"
28995		], factory );
28996	} else {
28997
28998		// Browser globals
28999		factory( jQuery );
29000	}
29001}( function( $ ) {
vendor: 34,618 bytes, lines 29001-30211
29001
29002
29003$.widget( "ui.draggable", $.ui.mouse, {
29004	version: "1.12.1",
29005	widgetEventPrefix: "drag",
29006	options: {
29007		addClasses: true,
29008		appendTo: "parent",
29009		axis: false,
29010		connectToSortable: false,
29011		containment: false,
29012		cursor: "auto",
29013		cursorAt: false,
29014		grid: false,
29015		handle: false,
29016		helper: "original",
29017		iframeFix: false,
29018		opacity: false,
29019		refreshPositions: false,
29020		revert: false,
29021		revertDuration: 500,
29022		scope: "default",
29023		scroll: true,
29024		scrollSensitivity: 20,
29025		scrollSpeed: 20,
29026		snap: false,
29027		snapMode: "both",
29028		snapTolerance: 20,
29029		stack: false,
29030		zIndex: false,
29031
29032		// Callbacks
29033		drag: null,
29034		start: null,
29035		stop: null
29036	},
29037	_create: function() {
29038
29039		if ( this.options.helper === "original" ) {
29040			this._setPositionRelative();
29041		}
29042		if ( this.options.addClasses ) {
29043			this._addClass( "ui-draggable" );
29044		}
29045		this._setHandleClassName();
29046
29047		this._mouseInit();
29048	},
29049
29050	_setOption: function( key, value ) {
29051		this._super( key, value );
29052		if ( key === "handle" ) {
29053			this._removeHandleClassName();
29054			this._setHandleClassName();
29055		}
29056	},
29057
29058	_destroy: function() {
29059		if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
29060			this.destroyOnClear = true;
29061			return;
29062		}
29063		this._removeHandleClassName();
29064		this._mouseDestroy();
29065	},
29066
29067	_mouseCapture: function( event ) {
29068		var o = this.options;
29069
29070		// Among others, prevent a drag on a resizable-handle
29071		if ( this.helper || o.disabled ||
29072				$( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) {
29073			return false;
29074		}
29075
29076		//Quit if we're not on a valid handle
29077		this.handle = this._getHandle( event );
29078		if ( !this.handle ) {
29079			return false;
29080		}
29081
29082		this._blurActiveElement( event );
29083
29084		this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );
29085
29086		return true;
29087
29088	},
29089
29090	_blockFrames: function( selector ) {
29091		this.iframeBlocks = this.document.find( selector ).map( function() {
29092			var iframe = $( this );
29093
29094			return $( "<div>" )
29095				.css( "position", "absolute" )
29096				.appendTo( iframe.parent() )
29097				.outerWidth( iframe.outerWidth() )
29098				.outerHeight( iframe.outerHeight() )
29099				.offset( iframe.offset() )[ 0 ];
29100		} );
29101	},
29102
29103	_unblockFrames: function() {
29104		if ( this.iframeBlocks ) {
29105			this.iframeBlocks.remove();
29106			delete this.iframeBlocks;
29107		}
29108	},
29109
29110	_blurActiveElement: function( event ) {
29111		var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
29112			target = $( event.target );
29113
29114		// Don't blur if the event occurred on an element that is within
29115		// the currently focused element
29116		// See #10527, #12472
29117		if ( target.closest( activeElement ).length ) {
29118			return;
29119		}
29120
29121		// Blur any element that currently has focus, see #4261
29122		$.ui.safeBlur( activeElement );
29123	},
29124
29125	_mouseStart: function( event ) {
29126
29127		var o = this.options;
29128
29129		//Create and append the visible helper
29130		this.helper = this._createHelper( event );
29131
29132		this._addClass( this.helper, "ui-draggable-dragging" );
29133
29134		//Cache the helper size
29135		this._cacheHelperProportions();
29136
29137		//If ddmanager is used for droppables, set the global draggable
29138		if ( $.ui.ddmanager ) {
29139			$.ui.ddmanager.current = this;
29140		}
29141
29142		/*
29143		 * - Position generation -
29144		 * This block generates everything position related - it's the core of draggables.
29145		 */
29146
29147		//Cache the margins of the original element
29148		this._cacheMargins();
29149
29150		//Store the helper's css position
29151		this.cssPosition = this.helper.css( "position" );
29152		this.scrollParent = this.helper.scrollParent( true );
29153		this.offsetParent = this.helper.offsetParent();
29154		this.hasFixedAncestor = this.helper.parents().filter( function() {
29155				return $( this ).css( "position" ) === "fixed";
29156			} ).length > 0;
29157
29158		//The element's absolute position on the page minus margins
29159		this.positionAbs = this.element.offset();
29160		this._refreshOffsets( event );
29161
29162		//Generate the original position
29163		this.originalPosition = this.position = this._generatePosition( event, false );
29164		this.originalPageX = event.pageX;
29165		this.originalPageY = event.pageY;
29166
29167		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
29168		( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
29169
29170		//Set a containment if given in the options
29171		this._setContainment();
29172
29173		//Trigger event + callbacks
29174		if ( this._trigger( "start", event ) === false ) {
29175			this._clear();
29176			return false;
29177		}
29178
29179		//Recache the helper size
29180		this._cacheHelperProportions();
29181
29182		//Prepare the droppable offsets
29183		if ( $.ui.ddmanager && !o.dropBehaviour ) {
29184			$.ui.ddmanager.prepareOffsets( this, event );
29185		}
29186
29187		// Execute the drag once - this causes the helper not to be visible before getting its
29188		// correct position
29189		this._mouseDrag( event, true );
29190
29191		// If the ddmanager is used for droppables, inform the manager that dragging has started
29192		// (see #5003)
29193		if ( $.ui.ddmanager ) {
29194			$.ui.ddmanager.dragStart( this, event );
29195		}
29196
29197		return true;
29198	},
29199
29200	_refreshOffsets: function( event ) {
29201		this.offset = {
29202			top: this.positionAbs.top - this.margins.top,
29203			left: this.positionAbs.left - this.margins.left,
29204			scroll: false,
29205			parent: this._getParentOffset(),
29206			relative: this._getRelativeOffset()
29207		};
29208
29209		this.offset.click = {
29210			left: event.pageX - this.offset.left,
29211			top: event.pageY - this.offset.top
29212		};
29213	},
29214
29215	_mouseDrag: function( event, noPropagation ) {
29216
29217		// reset any necessary cached properties (see #5009)
29218		if ( this.hasFixedAncestor ) {
29219			this.offset.parent = this._getParentOffset();
29220		}
29221
29222		//Compute the helpers position
29223		this.position = this._generatePosition( event, true );
29224		this.positionAbs = this._convertPositionTo( "absolute" );
29225
29226		//Call plugins and callbacks and use the resulting position if something is returned
29227		if ( !noPropagation ) {
29228			var ui = this._uiHash();
29229			if ( this._trigger( "drag", event, ui ) === false ) {
29230				this._mouseUp( new $.Event( "mouseup", event ) );
29231				return false;
29232			}
29233			this.position = ui.position;
29234		}
29235
29236		this.helper[ 0 ].style.left = this.position.left + "px";
29237		this.helper[ 0 ].style.top = this.position.top + "px";
29238
29239		if ( $.ui.ddmanager ) {
29240			$.ui.ddmanager.drag( this, event );
29241		}
29242
29243		return false;
29244	},
29245
29246	_mouseStop: function( event ) {
29247
29248		//If we are using droppables, inform the manager about the drop
29249		var that = this,
29250			dropped = false;
29251		if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
29252			dropped = $.ui.ddmanager.drop( this, event );
29253		}
29254
29255		//if a drop comes from outside (a sortable)
29256		if ( this.dropped ) {
29257			dropped = this.dropped;
29258			this.dropped = false;
29259		}
29260
29261		if ( ( this.options.revert === "invalid" && !dropped ) ||
29262				( this.options.revert === "valid" && dropped ) ||
29263				this.options.revert === true || ( $.isFunction( this.options.revert ) &&
29264				this.options.revert.call( this.element, dropped ) )
29265		) {
29266			$( this.helper ).animate(
29267				this.originalPosition,
29268				parseInt( this.options.revertDuration, 10 ),
29269				function() {
29270					if ( that._trigger( "stop", event ) !== false ) {
29271						that._clear();
29272					}
29273				}
29274			);
29275		} else {
29276			if ( this._trigger( "stop", event ) !== false ) {
29277				this._clear();
29278			}
29279		}
29280
29281		return false;
29282	},
29283
29284	_mouseUp: function( event ) {
29285		this._unblockFrames();
29286
29287		// If the ddmanager is used for droppables, inform the manager that dragging has stopped
29288		// (see #5003)
29289		if ( $.ui.ddmanager ) {
29290			$.ui.ddmanager.dragStop( this, event );
29291		}
29292
29293		// Only need to focus if the event occurred on the draggable itself, see #10527
29294		if ( this.handleElement.is( event.target ) ) {
29295
29296			// The interaction is over; whether or not the click resulted in a drag,
29297			// focus the element
29298			this.element.trigger( "focus" );
29299		}
29300
29301		return $.ui.mouse.prototype._mouseUp.call( this, event );
29302	},
29303
29304	cancel: function() {
29305
29306		if ( this.helper.is( ".ui-draggable-dragging" ) ) {
29307			this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) );
29308		} else {
29309			this._clear();
29310		}
29311
29312		return this;
29313
29314	},
29315
29316	_getHandle: function( event ) {
29317		return this.options.handle ?
29318			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
29319			true;
29320	},
29321
29322	_setHandleClassName: function() {
29323		this.handleElement = this.options.handle ?
29324			this.element.find( this.options.handle ) : this.element;
29325		this._addClass( this.handleElement, "ui-draggable-handle" );
29326	},
29327
29328	_removeHandleClassName: function() {
29329		this._removeClass( this.handleElement, "ui-draggable-handle" );
29330	},
29331
29332	_createHelper: function( event ) {
29333
29334		var o = this.options,
29335			helperIsFunction = $.isFunction( o.helper ),
29336			helper = helperIsFunction ?
29337				$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
29338				( o.helper === "clone" ?
29339					this.element.clone().removeAttr( "id" ) :
29340					this.element );
29341
29342		if ( !helper.parents( "body" ).length ) {
29343			helper.appendTo( ( o.appendTo === "parent" ?
29344				this.element[ 0 ].parentNode :
29345				o.appendTo ) );
29346		}
29347
29348		// Http://bugs.jqueryui.com/ticket/9446
29349		// a helper function can return the original element
29350		// which wouldn't have been set to relative in _create
29351		if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
29352			this._setPositionRelative();
29353		}
29354
29355		if ( helper[ 0 ] !== this.element[ 0 ] &&
29356				!( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) {
29357			helper.css( "position", "absolute" );
29358		}
29359
29360		return helper;
29361
29362	},
29363
29364	_setPositionRelative: function() {
29365		if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
29366			this.element[ 0 ].style.position = "relative";
29367		}
29368	},
29369
29370	_adjustOffsetFromHelper: function( obj ) {
29371		if ( typeof obj === "string" ) {
29372			obj = obj.split( " " );
29373		}
29374		if ( $.isArray( obj ) ) {
29375			obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
29376		}
29377		if ( "left" in obj ) {
29378			this.offset.click.left = obj.left + this.margins.left;
29379		}
29380		if ( "right" in obj ) {
29381			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
29382		}
29383		if ( "top" in obj ) {
29384			this.offset.click.top = obj.top + this.margins.top;
29385		}
29386		if ( "bottom" in obj ) {
29387			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
29388		}
29389	},
29390
29391	_isRootNode: function( element ) {
29392		return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
29393	},
29394
29395	_getParentOffset: function() {
29396
29397		//Get the offsetParent and cache its position
29398		var po = this.offsetParent.offset(),
29399			document = this.document[ 0 ];
29400
29401		// This is a special case where we need to modify a offset calculated on start, since the
29402		// following happened:
29403		// 1. The position of the helper is absolute, so it's position is calculated based on the
29404		// next positioned parent
29405		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
29406		// the document, which means that the scroll is included in the initial calculation of the
29407		// offset of the parent, and never recalculated upon drag
29408		if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document &&
29409				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
29410			po.left += this.scrollParent.scrollLeft();
29411			po.top += this.scrollParent.scrollTop();
29412		}
29413
29414		if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
29415			po = { top: 0, left: 0 };
29416		}
29417
29418		return {
29419			top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
29420			left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
29421		};
29422
29423	},
29424
29425	_getRelativeOffset: function() {
29426		if ( this.cssPosition !== "relative" ) {
29427			return { top: 0, left: 0 };
29428		}
29429
29430		var p = this.element.position(),
29431			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
29432
29433		return {
29434			top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
29435				( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
29436			left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
29437				( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
29438		};
29439
29440	},
29441
29442	_cacheMargins: function() {
29443		this.margins = {
29444			left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ),
29445			top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ),
29446			right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ),
29447			bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 )
29448		};
29449	},
29450
29451	_cacheHelperProportions: function() {
29452		this.helperProportions = {
29453			width: this.helper.outerWidth(),
29454			height: this.helper.outerHeight()
29455		};
29456	},
29457
29458	_setContainment: function() {
29459
29460		var isUserScrollable, c, ce,
29461			o = this.options,
29462			document = this.document[ 0 ];
29463
29464		this.relativeContainer = null;
29465
29466		if ( !o.containment ) {
29467			this.containment = null;
29468			return;
29469		}
29470
29471		if ( o.containment === "window" ) {
29472			this.containment = [
29473				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
29474				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
29475				$( window ).scrollLeft() + $( window ).width() -
29476					this.helperProportions.width - this.margins.left,
29477				$( window ).scrollTop() +
29478					( $( window ).height() || document.body.parentNode.scrollHeight ) -
29479					this.helperProportions.height - this.margins.top
29480			];
29481			return;
29482		}
29483
29484		if ( o.containment === "document" ) {
29485			this.containment = [
29486				0,
29487				0,
29488				$( document ).width() - this.helperProportions.width - this.margins.left,
29489				( $( document ).height() || document.body.parentNode.scrollHeight ) -
29490					this.helperProportions.height - this.margins.top
29491			];
29492			return;
29493		}
29494
29495		if ( o.containment.constructor === Array ) {
29496			this.containment = o.containment;
29497			return;
29498		}
29499
29500		if ( o.containment === "parent" ) {
29501			o.containment = this.helper[ 0 ].parentNode;
29502		}
29503
29504		c = $( o.containment );
29505		ce = c[ 0 ];
29506
29507		if ( !ce ) {
29508			return;
29509		}
29510
29511		isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );
29512
29513		this.containment = [
29514			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) +
29515				( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
29516			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) +
29517				( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
29518			( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
29519				( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
29520				( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
29521				this.helperProportions.width -
29522				this.margins.left -
29523				this.margins.right,
29524			( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
29525				( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
29526				( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
29527				this.helperProportions.height -
29528				this.margins.top -
29529				this.margins.bottom
29530		];
29531		this.relativeContainer = c;
29532	},
29533
29534	_convertPositionTo: function( d, pos ) {
29535
29536		if ( !pos ) {
29537			pos = this.position;
29538		}
29539
29540		var mod = d === "absolute" ? 1 : -1,
29541			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
29542
29543		return {
29544			top: (
29545
29546				// The absolute mouse position
29547				pos.top	+
29548
29549				// Only for relative positioned nodes: Relative offset from element to offset parent
29550				this.offset.relative.top * mod +
29551
29552				// The offsetParent's offset without borders (offset + border)
29553				this.offset.parent.top * mod -
29554				( ( this.cssPosition === "fixed" ?
29555					-this.offset.scroll.top :
29556					( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )
29557			),
29558			left: (
29559
29560				// The absolute mouse position
29561				pos.left +
29562
29563				// Only for relative positioned nodes: Relative offset from element to offset parent
29564				this.offset.relative.left * mod +
29565
29566				// The offsetParent's offset without borders (offset + border)
29567				this.offset.parent.left * mod	-
29568				( ( this.cssPosition === "fixed" ?
29569					-this.offset.scroll.left :
29570					( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )
29571			)
29572		};
29573
29574	},
29575
29576	_generatePosition: function( event, constrainPosition ) {
29577
29578		var containment, co, top, left,
29579			o = this.options,
29580			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
29581			pageX = event.pageX,
29582			pageY = event.pageY;
29583
29584		// Cache the scroll
29585		if ( !scrollIsRootNode || !this.offset.scroll ) {
29586			this.offset.scroll = {
29587				top: this.scrollParent.scrollTop(),
29588				left: this.scrollParent.scrollLeft()
29589			};
29590		}
29591
29592		/*
29593		 * - Position constraining -
29594		 * Constrain the position to a mix of grid, containment.
29595		 */
29596
29597		// If we are not dragging yet, we won't check for options
29598		if ( constrainPosition ) {
29599			if ( this.containment ) {
29600				if ( this.relativeContainer ) {
29601					co = this.relativeContainer.offset();
29602					containment = [
29603						this.containment[ 0 ] + co.left,
29604						this.containment[ 1 ] + co.top,
29605						this.containment[ 2 ] + co.left,
29606						this.containment[ 3 ] + co.top
29607					];
29608				} else {
29609					containment = this.containment;
29610				}
29611
29612				if ( event.pageX - this.offset.click.left < containment[ 0 ] ) {
29613					pageX = containment[ 0 ] + this.offset.click.left;
29614				}
29615				if ( event.pageY - this.offset.click.top < containment[ 1 ] ) {
29616					pageY = containment[ 1 ] + this.offset.click.top;
29617				}
29618				if ( event.pageX - this.offset.click.left > containment[ 2 ] ) {
29619					pageX = containment[ 2 ] + this.offset.click.left;
29620				}
29621				if ( event.pageY - this.offset.click.top > containment[ 3 ] ) {
29622					pageY = containment[ 3 ] + this.offset.click.top;
29623				}
29624			}
29625
29626			if ( o.grid ) {
29627
29628				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid
29629				// argument errors in IE (see ticket #6950)
29630				top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -
29631					this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;
29632				pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||
29633					top - this.offset.click.top > containment[ 3 ] ) ?
29634						top :
29635						( ( top - this.offset.click.top >= containment[ 1 ] ) ?
29636							top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;
29637
29638				left = o.grid[ 0 ] ? this.originalPageX +
29639					Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :
29640					this.originalPageX;
29641				pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||
29642					left - this.offset.click.left > containment[ 2 ] ) ?
29643						left :
29644						( ( left - this.offset.click.left >= containment[ 0 ] ) ?
29645							left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;
29646			}
29647
29648			if ( o.axis === "y" ) {
29649				pageX = this.originalPageX;
29650			}
29651
29652			if ( o.axis === "x" ) {
29653				pageY = this.originalPageY;
29654			}
29655		}
29656
29657		return {
29658			top: (
29659
29660				// The absolute mouse position
29661				pageY -
29662
29663				// Click offset (relative to the element)
29664				this.offset.click.top -
29665
29666				// Only for relative positioned nodes: Relative offset from element to offset parent
29667				this.offset.relative.top -
29668
29669				// The offsetParent's offset without borders (offset + border)
29670				this.offset.parent.top +
29671				( this.cssPosition === "fixed" ?
29672					-this.offset.scroll.top :
29673					( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
29674			),
29675			left: (
29676
29677				// The absolute mouse position
29678				pageX -
29679
29680				// Click offset (relative to the element)
29681				this.offset.click.left -
29682
29683				// Only for relative positioned nodes: Relative offset from element to offset parent
29684				this.offset.relative.left -
29685
29686				// The offsetParent's offset without borders (offset + border)
29687				this.offset.parent.left +
29688				( this.cssPosition === "fixed" ?
29689					-this.offset.scroll.left :
29690					( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
29691			)
29692		};
29693
29694	},
29695
29696	_clear: function() {
29697		this._removeClass( this.helper, "ui-draggable-dragging" );
29698		if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {
29699			this.helper.remove();
29700		}
29701		this.helper = null;
29702		this.cancelHelperRemoval = false;
29703		if ( this.destroyOnClear ) {
29704			this.destroy();
29705		}
29706	},
29707
29708	// From now on bulk stuff - mainly helpers
29709
29710	_trigger: function( type, event, ui ) {
29711		ui = ui || this._uiHash();
29712		$.ui.plugin.call( this, type, [ event, ui, this ], true );
29713
29714		// Absolute position and offset (see #6884 ) have to be recalculated after plugins
29715		if ( /^(drag|start|stop)/.test( type ) ) {
29716			this.positionAbs = this._convertPositionTo( "absolute" );
29717			ui.offset = this.positionAbs;
29718		}
29719		return $.Widget.prototype._trigger.call( this, type, event, ui );
29720	},
29721
29722	plugins: {},
29723
29724	_uiHash: function() {
29725		return {
29726			helper: this.helper,
29727			position: this.position,
29728			originalPosition: this.originalPosition,
29729			offset: this.positionAbs
29730		};
29731	}
29732
29733} );
29734
29735$.ui.plugin.add( "draggable", "connectToSortable", {
29736	start: function( event, ui, draggable ) {
29737		var uiSortable = $.extend( {}, ui, {
29738			item: draggable.element
29739		} );
29740
29741		draggable.sortables = [];
29742		$( draggable.options.connectToSortable ).each( function() {
29743			var sortable = $( this ).sortable( "instance" );
29744
29745			if ( sortable && !sortable.options.disabled ) {
29746				draggable.sortables.push( sortable );
29747
29748				// RefreshPositions is called at drag start to refresh the containerCache
29749				// which is used in drag. This ensures it's initialized and synchronized
29750				// with any changes that might have happened on the page since initialization.
29751				sortable.refreshPositions();
29752				sortable._trigger( "activate", event, uiSortable );
29753			}
29754		} );
29755	},
29756	stop: function( event, ui, draggable ) {
29757		var uiSortable = $.extend( {}, ui, {
29758			item: draggable.element
29759		} );
29760
29761		draggable.cancelHelperRemoval = false;
29762
29763		$.each( draggable.sortables, function() {
29764			var sortable = this;
29765
29766			if ( sortable.isOver ) {
29767				sortable.isOver = 0;
29768
29769				// Allow this sortable to handle removing the helper
29770				draggable.cancelHelperRemoval = true;
29771				sortable.cancelHelperRemoval = false;
29772
29773				// Use _storedCSS To restore properties in the sortable,
29774				// as this also handles revert (#9675) since the draggable
29775				// may have modified them in unexpected ways (#8809)
29776				sortable._storedCSS = {
29777					position: sortable.placeholder.css( "position" ),
29778					top: sortable.placeholder.css( "top" ),
29779					left: sortable.placeholder.css( "left" )
29780				};
29781
29782				sortable._mouseStop( event );
29783
29784				// Once drag has ended, the sortable should return to using
29785				// its original helper, not the shared helper from draggable
29786				sortable.options.helper = sortable.options._helper;
29787			} else {
29788
29789				// Prevent this Sortable from removing the helper.
29790				// However, don't set the draggable to remove the helper
29791				// either as another connected Sortable may yet handle the removal.
29792				sortable.cancelHelperRemoval = true;
29793
29794				sortable._trigger( "deactivate", event, uiSortable );
29795			}
29796		} );
29797	},
29798	drag: function( event, ui, draggable ) {
29799		$.each( draggable.sortables, function() {
29800			var innermostIntersecting = false,
29801				sortable = this;
29802
29803			// Copy over variables that sortable's _intersectsWith uses
29804			sortable.positionAbs = draggable.positionAbs;
29805			sortable.helperProportions = draggable.helperProportions;
29806			sortable.offset.click = draggable.offset.click;
29807
29808			if ( sortable._intersectsWith( sortable.containerCache ) ) {
29809				innermostIntersecting = true;
29810
29811				$.each( draggable.sortables, function() {
29812
29813					// Copy over variables that sortable's _intersectsWith uses
29814					this.positionAbs = draggable.positionAbs;
29815					this.helperProportions = draggable.helperProportions;
29816					this.offset.click = draggable.offset.click;
29817
29818					if ( this !== sortable &&
29819							this._intersectsWith( this.containerCache ) &&
29820							$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
29821						innermostIntersecting = false;
29822					}
29823
29824					return innermostIntersecting;
29825				} );
29826			}
29827
29828			if ( innermostIntersecting ) {
29829
29830				// If it intersects, we use a little isOver variable and set it once,
29831				// so that the move-in stuff gets fired only once.
29832				if ( !sortable.isOver ) {
29833					sortable.isOver = 1;
29834
29835					// Store draggable's parent in case we need to reappend to it later.
29836					draggable._parent = ui.helper.parent();
29837
29838					sortable.currentItem = ui.helper
29839						.appendTo( sortable.element )
29840						.data( "ui-sortable-item", true );
29841
29842					// Store helper option to later restore it
29843					sortable.options._helper = sortable.options.helper;
29844
29845					sortable.options.helper = function() {
29846						return ui.helper[ 0 ];
29847					};
29848
29849					// Fire the start events of the sortable with our passed browser event,
29850					// and our own helper (so it doesn't create a new one)
29851					event.target = sortable.currentItem[ 0 ];
29852					sortable._mouseCapture( event, true );
29853					sortable._mouseStart( event, true, true );
29854
29855					// Because the browser event is way off the new appended portlet,
29856					// modify necessary variables to reflect the changes
29857					sortable.offset.click.top = draggable.offset.click.top;
29858					sortable.offset.click.left = draggable.offset.click.left;
29859					sortable.offset.parent.left -= draggable.offset.parent.left -
29860						sortable.offset.parent.left;
29861					sortable.offset.parent.top -= draggable.offset.parent.top -
29862						sortable.offset.parent.top;
29863
29864					draggable._trigger( "toSortable", event );
29865
29866					// Inform draggable that the helper is in a valid drop zone,
29867					// used solely in the revert option to handle "valid/invalid".
29868					draggable.dropped = sortable.element;
29869
29870					// Need to refreshPositions of all sortables in the case that
29871					// adding to one sortable changes the location of the other sortables (#9675)
29872					$.each( draggable.sortables, function() {
29873						this.refreshPositions();
29874					} );
29875
29876					// Hack so receive/update callbacks work (mostly)
29877					draggable.currentItem = draggable.element;
29878					sortable.fromOutside = draggable;
29879				}
29880
29881				if ( sortable.currentItem ) {
29882					sortable._mouseDrag( event );
29883
29884					// Copy the sortable's position because the draggable's can potentially reflect
29885					// a relative position, while sortable is always absolute, which the dragged
29886					// element has now become. (#8809)
29887					ui.position = sortable.position;
29888				}
29889			} else {
29890
29891				// If it doesn't intersect with the sortable, and it intersected before,
29892				// we fake the drag stop of the sortable, but make sure it doesn't remove
29893				// the helper by using cancelHelperRemoval.
29894				if ( sortable.isOver ) {
29895
29896					sortable.isOver = 0;
29897					sortable.cancelHelperRemoval = true;
29898
29899					// Calling sortable's mouseStop would trigger a revert,
29900					// so revert must be temporarily false until after mouseStop is called.
29901					sortable.options._revert = sortable.options.revert;
29902					sortable.options.revert = false;
29903
29904					sortable._trigger( "out", event, sortable._uiHash( sortable ) );
29905					sortable._mouseStop( event, true );
29906
29907					// Restore sortable behaviors that were modfied
29908					// when the draggable entered the sortable area (#9481)
29909					sortable.options.revert = sortable.options._revert;
29910					sortable.options.helper = sortable.options._helper;
29911
29912					if ( sortable.placeholder ) {
29913						sortable.placeholder.remove();
29914					}
29915
29916					// Restore and recalculate the draggable's offset considering the sortable
29917					// may have modified them in unexpected ways. (#8809, #10669)
29918					ui.helper.appendTo( draggable._parent );
29919					draggable._refreshOffsets( event );
29920					ui.position = draggable._generatePosition( event, true );
29921
29922					draggable._trigger( "fromSortable", event );
29923
29924					// Inform draggable that the helper is no longer in a valid drop zone
29925					draggable.dropped = false;
29926
29927					// Need to refreshPositions of all sortables just in case removing
29928					// from one sortable changes the location of other sortables (#9675)
29929					$.each( draggable.sortables, function() {
29930						this.refreshPositions();
29931					} );
29932				}
29933			}
29934		} );
29935	}
29936} );
29937
29938$.ui.plugin.add( "draggable", "cursor", {
29939	start: function( event, ui, instance ) {
29940		var t = $( "body" ),
29941			o = instance.options;
29942
29943		if ( t.css( "cursor" ) ) {
29944			o._cursor = t.css( "cursor" );
29945		}
29946		t.css( "cursor", o.cursor );
29947	},
29948	stop: function( event, ui, instance ) {
29949		var o = instance.options;
29950		if ( o._cursor ) {
29951			$( "body" ).css( "cursor", o._cursor );
29952		}
29953	}
29954} );
29955
29956$.ui.plugin.add( "draggable", "opacity", {
29957	start: function( event, ui, instance ) {
29958		var t = $( ui.helper ),
29959			o = instance.options;
29960		if ( t.css( "opacity" ) ) {
29961			o._opacity = t.css( "opacity" );
29962		}
29963		t.css( "opacity", o.opacity );
29964	},
29965	stop: function( event, ui, instance ) {
29966		var o = instance.options;
29967		if ( o._opacity ) {
29968			$( ui.helper ).css( "opacity", o._opacity );
29969		}
29970	}
29971} );
29972
29973$.ui.plugin.add( "draggable", "scroll", {
29974	start: function( event, ui, i ) {
29975		if ( !i.scrollParentNotHidden ) {
29976			i.scrollParentNotHidden = i.helper.scrollParent( false );
29977		}
29978
29979		if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&
29980				i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
29981			i.overflowOffset = i.scrollParentNotHidden.offset();
29982		}
29983	},
29984	drag: function( event, ui, i  ) {
29985
29986		var o = i.options,
29987			scrolled = false,
29988			scrollParent = i.scrollParentNotHidden[ 0 ],
29989			document = i.document[ 0 ];
29990
29991		if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
29992			if ( !o.axis || o.axis !== "x" ) {
29993				if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <
29994						o.scrollSensitivity ) {
29995					scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
29996				} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
29997					scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
29998				}
29999			}
30000
30001			if ( !o.axis || o.axis !== "y" ) {
30002				if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <
30003						o.scrollSensitivity ) {
30004					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
30005				} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
30006					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
30007				}
30008			}
30009
30010		} else {
30011
30012			if ( !o.axis || o.axis !== "x" ) {
30013				if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {
30014					scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );
30015				} else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <
30016						o.scrollSensitivity ) {
30017					scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );
30018				}
30019			}
30020
30021			if ( !o.axis || o.axis !== "y" ) {
30022				if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {
30023					scrolled = $( document ).scrollLeft(
30024						$( document ).scrollLeft() - o.scrollSpeed
30025					);
30026				} else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <
30027						o.scrollSensitivity ) {
30028					scrolled = $( document ).scrollLeft(
30029						$( document ).scrollLeft() + o.scrollSpeed
30030					);
30031				}
30032			}
30033
30034		}
30035
30036		if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
30037			$.ui.ddmanager.prepareOffsets( i, event );
30038		}
30039
30040	}
30041} );
30042
30043$.ui.plugin.add( "draggable", "snap", {
30044	start: function( event, ui, i ) {
30045
30046		var o = i.options;
30047
30048		i.snapElements = [];
30049
30050		$( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap )
30051			.each( function() {
30052				var $t = $( this ),
30053					$o = $t.offset();
30054				if ( this !== i.element[ 0 ] ) {
30055					i.snapElements.push( {
30056						item: this,
30057						width: $t.outerWidth(), height: $t.outerHeight(),
30058						top: $o.top, left: $o.left
30059					} );
30060				}
30061			} );
30062
30063	},
30064	drag: function( event, ui, inst ) {
30065
30066		var ts, bs, ls, rs, l, r, t, b, i, first,
30067			o = inst.options,
30068			d = o.snapTolerance,
30069			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
30070			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
30071
30072		for ( i = inst.snapElements.length - 1; i >= 0; i-- ) {
30073
30074			l = inst.snapElements[ i ].left - inst.margins.left;
30075			r = l + inst.snapElements[ i ].width;
30076			t = inst.snapElements[ i ].top - inst.margins.top;
30077			b = t + inst.snapElements[ i ].height;
30078
30079			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||
30080					!$.contains( inst.snapElements[ i ].item.ownerDocument,
30081					inst.snapElements[ i ].item ) ) {
30082				if ( inst.snapElements[ i ].snapping ) {
30083					( inst.options.snap.release &&
30084						inst.options.snap.release.call(
30085							inst.element,
30086							event,
30087							$.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )
30088						) );
30089				}
30090				inst.snapElements[ i ].snapping = false;
30091				continue;
30092			}
30093
30094			if ( o.snapMode !== "inner" ) {
30095				ts = Math.abs( t - y2 ) <= d;
30096				bs = Math.abs( b - y1 ) <= d;
30097				ls = Math.abs( l - x2 ) <= d;
30098				rs = Math.abs( r - x1 ) <= d;
30099				if ( ts ) {
30100					ui.position.top = inst._convertPositionTo( "relative", {
30101						top: t - inst.helperProportions.height,
30102						left: 0
30103					} ).top;
30104				}
30105				if ( bs ) {
30106					ui.position.top = inst._convertPositionTo( "relative", {
30107						top: b,
30108						left: 0
30109					} ).top;
30110				}
30111				if ( ls ) {
30112					ui.position.left = inst._convertPositionTo( "relative", {
30113						top: 0,
30114						left: l - inst.helperProportions.width
30115					} ).left;
30116				}
30117				if ( rs ) {
30118					ui.position.left = inst._convertPositionTo( "relative", {
30119						top: 0,
30120						left: r
30121					} ).left;
30122				}
30123			}
30124
30125			first = ( ts || bs || ls || rs );
30126
30127			if ( o.snapMode !== "outer" ) {
30128				ts = Math.abs( t - y1 ) <= d;
30129				bs = Math.abs( b - y2 ) <= d;
30130				ls = Math.abs( l - x1 ) <= d;
30131				rs = Math.abs( r - x2 ) <= d;
30132				if ( ts ) {
30133					ui.position.top = inst._convertPositionTo( "relative", {
30134						top: t,
30135						left: 0
30136					} ).top;
30137				}
30138				if ( bs ) {
30139					ui.position.top = inst._convertPositionTo( "relative", {
30140						top: b - inst.helperProportions.height,
30141						left: 0
30142					} ).top;
30143				}
30144				if ( ls ) {
30145					ui.position.left = inst._convertPositionTo( "relative", {
30146						top: 0,
30147						left: l
30148					} ).left;
30149				}
30150				if ( rs ) {
30151					ui.position.left = inst._convertPositionTo( "relative", {
30152						top: 0,
30153						left: r - inst.helperProportions.width
30154					} ).left;
30155				}
30156			}
30157
30158			if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {
30159				( inst.options.snap.snap &&
30160					inst.options.snap.snap.call(
30161						inst.element,
30162						event,
30163						$.extend( inst._uiHash(), {
30164							snapItem: inst.snapElements[ i ].item
30165						} ) ) );
30166			}
30167			inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );
30168
30169		}
30170
30171	}
30172} );
30173
30174$.ui.plugin.add( "draggable", "stack", {
30175	start: function( event, ui, instance ) {
30176		var min,
30177			o = instance.options,
30178			group = $.makeArray( $( o.stack ) ).sort( function( a, b ) {
30179				return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) -
30180					( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 );
30181			} );
30182
30183		if ( !group.length ) { return; }
30184
30185		min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0;
30186		$( group ).each( function( i ) {
30187			$( this ).css( "zIndex", min + i );
30188		} );
30189		this.css( "zIndex", ( min + group.length ) );
30190	}
30191} );
30192
30193$.ui.plugin.add( "draggable", "zIndex", {
30194	start: function( event, ui, instance ) {
30195		var t = $( ui.helper ),
30196			o = instance.options;
30197
30198		if ( t.css( "zIndex" ) ) {
30199			o._zIndex = t.css( "zIndex" );
30200		}
30201		t.css( "zIndex", o.zIndex );
30202	},
30203	stop: function( event, ui, instance ) {
30204		var o = instance.options;
30205
30206		if ( o._zIndex ) {
30207			$( ui.helper ).css( "zIndex", o._zIndex );
30208		}
30209	}
30210} );
30211
30212return $.ui.draggable;
30213
30214} ) );
30215
30216
30217
30218
30219
30220
30221/*!
30222 * jQuery UI Resizable 1.12.1
30223 * http://jqueryui.com
30224 *
30225 * Copyright jQuery Foundation and other contributors
30226 * Released under the MIT license.
30227 * http://jquery.org/license
30228 */
30229
30230//>>label: Resizable
30231//>>group: Interactions
30232//>>description: Enables resize functionality for any element.
30233//>>docs: http://api.jqueryui.com/resizable/
30234//>>demos: http://jqueryui.com/resizable/
30235//>>css.structure: ../../themes/base/core.css
30236//>>css.structure: ../../themes/base/resizable.css
30237//>>css.theme: ../../themes/base/theme.css
30238
30239( function( factory ) {
30240	if ( typeof define === "function" && define.amd ) {
30241
30242		// AMD. Register as an anonymous module.
30243		define( [
30244			"jquery",
30245			"./mouse",
30246			"../disable-selection",
30247			"../plugin",
30248			"../version",
30249			"../widget"
30250		], factory );
30251	} else {
30252
30253		// Browser globals
30254		factory( jQuery );
30255	}
30256}( function( $ ) {
vendor: 29,112 bytes, lines 30256-31418
30256
30257
30258$.widget( "ui.resizable", $.ui.mouse, {
30259	version: "1.12.1",
30260	widgetEventPrefix: "resize",
30261	options: {
30262		alsoResize: false,
30263		animate: false,
30264		animateDuration: "slow",
30265		animateEasing: "swing",
30266		aspectRatio: false,
30267		autoHide: false,
30268		classes: {
30269			"ui-resizable-se": "ui-icon ui-icon-gripsmall-diagonal-se"
30270		},
30271		containment: false,
30272		ghost: false,
30273		grid: false,
30274		handles: "e,s,se",
30275		helper: false,
30276		maxHeight: null,
30277		maxWidth: null,
30278		minHeight: 10,
30279		minWidth: 10,
30280
30281		// See #7960
30282		zIndex: 90,
30283
30284		// Callbacks
30285		resize: null,
30286		start: null,
30287		stop: null
30288	},
30289
30290	_num: function( value ) {
30291		return parseFloat( value ) || 0;
30292	},
30293
30294	_isNumber: function( value ) {
30295		return !isNaN( parseFloat( value ) );
30296	},
30297
30298	_hasScroll: function( el, a ) {
30299
30300		if ( $( el ).css( "overflow" ) === "hidden" ) {
30301			return false;
30302		}
30303
30304		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
30305			has = false;
30306
30307		if ( el[ scroll ] > 0 ) {
30308			return true;
30309		}
30310
30311		// TODO: determine which cases actually cause this to happen
30312		// if the element doesn't have the scroll set, see if it's possible to
30313		// set the scroll
30314		el[ scroll ] = 1;
30315		has = ( el[ scroll ] > 0 );
30316		el[ scroll ] = 0;
30317		return has;
30318	},
30319
30320	_create: function() {
30321
30322		var margins,
30323			o = this.options,
30324			that = this;
30325		this._addClass( "ui-resizable" );
30326
30327		$.extend( this, {
30328			_aspectRatio: !!( o.aspectRatio ),
30329			aspectRatio: o.aspectRatio,
30330			originalElement: this.element,
30331			_proportionallyResizeElements: [],
30332			_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
30333		} );
30334
30335		// Wrap the element if it cannot hold child nodes
30336		if ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) {
30337
30338			this.element.wrap(
30339				$( "<div class='ui-wrapper' style='overflow: hidden;'></div>" ).css( {
30340					position: this.element.css( "position" ),
30341					width: this.element.outerWidth(),
30342					height: this.element.outerHeight(),
30343					top: this.element.css( "top" ),
30344					left: this.element.css( "left" )
30345				} )
30346			);
30347
30348			this.element = this.element.parent().data(
30349				"ui-resizable", this.element.resizable( "instance" )
30350			);
30351
30352			this.elementIsWrapper = true;
30353
30354			margins = {
30355				marginTop: this.originalElement.css( "marginTop" ),
30356				marginRight: this.originalElement.css( "marginRight" ),
30357				marginBottom: this.originalElement.css( "marginBottom" ),
30358				marginLeft: this.originalElement.css( "marginLeft" )
30359			};
30360
30361			this.element.css( margins );
30362			this.originalElement.css( "margin", 0 );
30363
30364			// support: Safari
30365			// Prevent Safari textarea resize
30366			this.originalResizeStyle = this.originalElement.css( "resize" );
30367			this.originalElement.css( "resize", "none" );
30368
30369			this._proportionallyResizeElements.push( this.originalElement.css( {
30370				position: "static",
30371				zoom: 1,
30372				display: "block"
30373			} ) );
30374
30375			// Support: IE9
30376			// avoid IE jump (hard set the margin)
30377			this.originalElement.css( margins );
30378
30379			this._proportionallyResize();
30380		}
30381
30382		this._setupHandles();
30383
30384		if ( o.autoHide ) {
30385			$( this.element )
30386				.on( "mouseenter", function() {
30387					if ( o.disabled ) {
30388						return;
30389					}
30390					that._removeClass( "ui-resizable-autohide" );
30391					that._handles.show();
30392				} )
30393				.on( "mouseleave", function() {
30394					if ( o.disabled ) {
30395						return;
30396					}
30397					if ( !that.resizing ) {
30398						that._addClass( "ui-resizable-autohide" );
30399						that._handles.hide();
30400					}
30401				} );
30402		}
30403
30404		this._mouseInit();
30405	},
30406
30407	_destroy: function() {
30408
30409		this._mouseDestroy();
30410
30411		var wrapper,
30412			_destroy = function( exp ) {
30413				$( exp )
30414					.removeData( "resizable" )
30415					.removeData( "ui-resizable" )
30416					.off( ".resizable" )
30417					.find( ".ui-resizable-handle" )
30418						.remove();
30419			};
30420
30421		// TODO: Unwrap at same DOM position
30422		if ( this.elementIsWrapper ) {
30423			_destroy( this.element );
30424			wrapper = this.element;
30425			this.originalElement.css( {
30426				position: wrapper.css( "position" ),
30427				width: wrapper.outerWidth(),
30428				height: wrapper.outerHeight(),
30429				top: wrapper.css( "top" ),
30430				left: wrapper.css( "left" )
30431			} ).insertAfter( wrapper );
30432			wrapper.remove();
30433		}
30434
30435		this.originalElement.css( "resize", this.originalResizeStyle );
30436		_destroy( this.originalElement );
30437
30438		return this;
30439	},
30440
30441	_setOption: function( key, value ) {
30442		this._super( key, value );
30443
30444		switch ( key ) {
30445		case "handles":
30446			this._removeHandles();
30447			this._setupHandles();
30448			break;
30449		default:
30450			break;
30451		}
30452	},
30453
30454	_setupHandles: function() {
30455		var o = this.options, handle, i, n, hname, axis, that = this;
30456		this.handles = o.handles ||
30457			( !$( ".ui-resizable-handle", this.element ).length ?
30458				"e,s,se" : {
30459					n: ".ui-resizable-n",
30460					e: ".ui-resizable-e",
30461					s: ".ui-resizable-s",
30462					w: ".ui-resizable-w",
30463					se: ".ui-resizable-se",
30464					sw: ".ui-resizable-sw",
30465					ne: ".ui-resizable-ne",
30466					nw: ".ui-resizable-nw"
30467				} );
30468
30469		this._handles = $();
30470		if ( this.handles.constructor === String ) {
30471
30472			if ( this.handles === "all" ) {
30473				this.handles = "n,e,s,w,se,sw,ne,nw";
30474			}
30475
30476			n = this.handles.split( "," );
30477			this.handles = {};
30478
30479			for ( i = 0; i < n.length; i++ ) {
30480
30481				handle = $.trim( n[ i ] );
30482				hname = "ui-resizable-" + handle;
30483				axis = $( "<div>" );
30484				this._addClass( axis, "ui-resizable-handle " + hname );
30485
30486				axis.css( { zIndex: o.zIndex } );
30487
30488				this.handles[ handle ] = ".ui-resizable-" + handle;
30489				this.element.append( axis );
30490			}
30491
30492		}
30493
30494		this._renderAxis = function( target ) {
30495
30496			var i, axis, padPos, padWrapper;
30497
30498			target = target || this.element;
30499
30500			for ( i in this.handles ) {
30501
30502				if ( this.handles[ i ].constructor === String ) {
30503					this.handles[ i ] = this.element.children( this.handles[ i ] ).first().show();
30504				} else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) {
30505					this.handles[ i ] = $( this.handles[ i ] );
30506					this._on( this.handles[ i ], { "mousedown": that._mouseDown } );
30507				}
30508
30509				if ( this.elementIsWrapper &&
30510						this.originalElement[ 0 ]
30511							.nodeName
30512							.match( /^(textarea|input|select|button)$/i ) ) {
30513					axis = $( this.handles[ i ], this.element );
30514
30515					padWrapper = /sw|ne|nw|se|n|s/.test( i ) ?
30516						axis.outerHeight() :
30517						axis.outerWidth();
30518
30519					padPos = [ "padding",
30520						/ne|nw|n/.test( i ) ? "Top" :
30521						/se|sw|s/.test( i ) ? "Bottom" :
30522						/^e$/.test( i ) ? "Right" : "Left" ].join( "" );
30523
30524					target.css( padPos, padWrapper );
30525
30526					this._proportionallyResize();
30527				}
30528
30529				this._handles = this._handles.add( this.handles[ i ] );
30530			}
30531		};
30532
30533		// TODO: make renderAxis a prototype function
30534		this._renderAxis( this.element );
30535
30536		this._handles = this._handles.add( this.element.find( ".ui-resizable-handle" ) );
30537		this._handles.disableSelection();
30538
30539		this._handles.on( "mouseover", function() {
30540			if ( !that.resizing ) {
30541				if ( this.className ) {
30542					axis = this.className.match( /ui-resizable-(se|sw|ne|nw|n|e|s|w)/i );
30543				}
30544				that.axis = axis && axis[ 1 ] ? axis[ 1 ] : "se";
30545			}
30546		} );
30547
30548		if ( o.autoHide ) {
30549			this._handles.hide();
30550			this._addClass( "ui-resizable-autohide" );
30551		}
30552	},
30553
30554	_removeHandles: function() {
30555		this._handles.remove();
30556	},
30557
30558	_mouseCapture: function( event ) {
30559		var i, handle,
30560			capture = false;
30561
30562		for ( i in this.handles ) {
30563			handle = $( this.handles[ i ] )[ 0 ];
30564			if ( handle === event.target || $.contains( handle, event.target ) ) {
30565				capture = true;
30566			}
30567		}
30568
30569		return !this.options.disabled && capture;
30570	},
30571
30572	_mouseStart: function( event ) {
30573
30574		var curleft, curtop, cursor,
30575			o = this.options,
30576			el = this.element;
30577
30578		this.resizing = true;
30579
30580		this._renderProxy();
30581
30582		curleft = this._num( this.helper.css( "left" ) );
30583		curtop = this._num( this.helper.css( "top" ) );
30584
30585		if ( o.containment ) {
30586			curleft += $( o.containment ).scrollLeft() || 0;
30587			curtop += $( o.containment ).scrollTop() || 0;
30588		}
30589
30590		this.offset = this.helper.offset();
30591		this.position = { left: curleft, top: curtop };
30592
30593		this.size = this._helper ? {
30594				width: this.helper.width(),
30595				height: this.helper.height()
30596			} : {
30597				width: el.width(),
30598				height: el.height()
30599			};
30600
30601		this.originalSize = this._helper ? {
30602				width: el.outerWidth(),
30603				height: el.outerHeight()
30604			} : {
30605				width: el.width(),
30606				height: el.height()
30607			};
30608
30609		this.sizeDiff = {
30610			width: el.outerWidth() - el.width(),
30611			height: el.outerHeight() - el.height()
30612		};
30613
30614		this.originalPosition = { left: curleft, top: curtop };
30615		this.originalMousePosition = { left: event.pageX, top: event.pageY };
30616
30617		this.aspectRatio = ( typeof o.aspectRatio === "number" ) ?
30618			o.aspectRatio :
30619			( ( this.originalSize.width / this.originalSize.height ) || 1 );
30620
30621		cursor = $( ".ui-resizable-" + this.axis ).css( "cursor" );
30622		$( "body" ).css( "cursor", cursor === "auto" ? this.axis + "-resize" : cursor );
30623
30624		this._addClass( "ui-resizable-resizing" );
30625		this._propagate( "start", event );
30626		return true;
30627	},
30628
30629	_mouseDrag: function( event ) {
30630
30631		var data, props,
30632			smp = this.originalMousePosition,
30633			a = this.axis,
30634			dx = ( event.pageX - smp.left ) || 0,
30635			dy = ( event.pageY - smp.top ) || 0,
30636			trigger = this._change[ a ];
30637
30638		this._updatePrevProperties();
30639
30640		if ( !trigger ) {
30641			return false;
30642		}
30643
30644		data = trigger.apply( this, [ event, dx, dy ] );
30645
30646		this._updateVirtualBoundaries( event.shiftKey );
30647		if ( this._aspectRatio || event.shiftKey ) {
30648			data = this._updateRatio( data, event );
30649		}
30650
30651		data = this._respectSize( data, event );
30652
30653		this._updateCache( data );
30654
30655		this._propagate( "resize", event );
30656
30657		props = this._applyChanges();
30658
30659		if ( !this._helper && this._proportionallyResizeElements.length ) {
30660			this._proportionallyResize();
30661		}
30662
30663		if ( !$.isEmptyObject( props ) ) {
30664			this._updatePrevProperties();
30665			this._trigger( "resize", event, this.ui() );
30666			this._applyChanges();
30667		}
30668
30669		return false;
30670	},
30671
30672	_mouseStop: function( event ) {
30673
30674		this.resizing = false;
30675		var pr, ista, soffseth, soffsetw, s, left, top,
30676			o = this.options, that = this;
30677
30678		if ( this._helper ) {
30679
30680			pr = this._proportionallyResizeElements;
30681			ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName );
30682			soffseth = ista && this._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height;
30683			soffsetw = ista ? 0 : that.sizeDiff.width;
30684
30685			s = {
30686				width: ( that.helper.width()  - soffsetw ),
30687				height: ( that.helper.height() - soffseth )
30688			};
30689			left = ( parseFloat( that.element.css( "left" ) ) +
30690				( that.position.left - that.originalPosition.left ) ) || null;
30691			top = ( parseFloat( that.element.css( "top" ) ) +
30692				( that.position.top - that.originalPosition.top ) ) || null;
30693
30694			if ( !o.animate ) {
30695				this.element.css( $.extend( s, { top: top, left: left } ) );
30696			}
30697
30698			that.helper.height( that.size.height );
30699			that.helper.width( that.size.width );
30700
30701			if ( this._helper && !o.animate ) {
30702				this._proportionallyResize();
30703			}
30704		}
30705
30706		$( "body" ).css( "cursor", "auto" );
30707
30708		this._removeClass( "ui-resizable-resizing" );
30709
30710		this._propagate( "stop", event );
30711
30712		if ( this._helper ) {
30713			this.helper.remove();
30714		}
30715
30716		return false;
30717
30718	},
30719
30720	_updatePrevProperties: function() {
30721		this.prevPosition = {
30722			top: this.position.top,
30723			left: this.position.left
30724		};
30725		this.prevSize = {
30726			width: this.size.width,
30727			height: this.size.height
30728		};
30729	},
30730
30731	_applyChanges: function() {
30732		var props = {};
30733
30734		if ( this.position.top !== this.prevPosition.top ) {
30735			props.top = this.position.top + "px";
30736		}
30737		if ( this.position.left !== this.prevPosition.left ) {
30738			props.left = this.position.left + "px";
30739		}
30740		if ( this.size.width !== this.prevSize.width ) {
30741			props.width = this.size.width + "px";
30742		}
30743		if ( this.size.height !== this.prevSize.height ) {
30744			props.height = this.size.height + "px";
30745		}
30746
30747		this.helper.css( props );
30748
30749		return props;
30750	},
30751
30752	_updateVirtualBoundaries: function( forceAspectRatio ) {
30753		var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
30754			o = this.options;
30755
30756		b = {
30757			minWidth: this._isNumber( o.minWidth ) ? o.minWidth : 0,
30758			maxWidth: this._isNumber( o.maxWidth ) ? o.maxWidth : Infinity,
30759			minHeight: this._isNumber( o.minHeight ) ? o.minHeight : 0,
30760			maxHeight: this._isNumber( o.maxHeight ) ? o.maxHeight : Infinity
30761		};
30762
30763		if ( this._aspectRatio || forceAspectRatio ) {
30764			pMinWidth = b.minHeight * this.aspectRatio;
30765			pMinHeight = b.minWidth / this.aspectRatio;
30766			pMaxWidth = b.maxHeight * this.aspectRatio;
30767			pMaxHeight = b.maxWidth / this.aspectRatio;
30768
30769			if ( pMinWidth > b.minWidth ) {
30770				b.minWidth = pMinWidth;
30771			}
30772			if ( pMinHeight > b.minHeight ) {
30773				b.minHeight = pMinHeight;
30774			}
30775			if ( pMaxWidth < b.maxWidth ) {
30776				b.maxWidth = pMaxWidth;
30777			}
30778			if ( pMaxHeight < b.maxHeight ) {
30779				b.maxHeight = pMaxHeight;
30780			}
30781		}
30782		this._vBoundaries = b;
30783	},
30784
30785	_updateCache: function( data ) {
30786		this.offset = this.helper.offset();
30787		if ( this._isNumber( data.left ) ) {
30788			this.position.left = data.left;
30789		}
30790		if ( this._isNumber( data.top ) ) {
30791			this.position.top = data.top;
30792		}
30793		if ( this._isNumber( data.height ) ) {
30794			this.size.height = data.height;
30795		}
30796		if ( this._isNumber( data.width ) ) {
30797			this.size.width = data.width;
30798		}
30799	},
30800
30801	_updateRatio: function( data ) {
30802
30803		var cpos = this.position,
30804			csize = this.size,
30805			a = this.axis;
30806
30807		if ( this._isNumber( data.height ) ) {
30808			data.width = ( data.height * this.aspectRatio );
30809		} else if ( this._isNumber( data.width ) ) {
30810			data.height = ( data.width / this.aspectRatio );
30811		}
30812
30813		if ( a === "sw" ) {
30814			data.left = cpos.left + ( csize.width - data.width );
30815			data.top = null;
30816		}
30817		if ( a === "nw" ) {
30818			data.top = cpos.top + ( csize.height - data.height );
30819			data.left = cpos.left + ( csize.width - data.width );
30820		}
30821
30822		return data;
30823	},
30824
30825	_respectSize: function( data ) {
30826
30827		var o = this._vBoundaries,
30828			a = this.axis,
30829			ismaxw = this._isNumber( data.width ) && o.maxWidth && ( o.maxWidth < data.width ),
30830			ismaxh = this._isNumber( data.height ) && o.maxHeight && ( o.maxHeight < data.height ),
30831			isminw = this._isNumber( data.width ) && o.minWidth && ( o.minWidth > data.width ),
30832			isminh = this._isNumber( data.height ) && o.minHeight && ( o.minHeight > data.height ),
30833			dw = this.originalPosition.left + this.originalSize.width,
30834			dh = this.originalPosition.top + this.originalSize.height,
30835			cw = /sw|nw|w/.test( a ), ch = /nw|ne|n/.test( a );
30836		if ( isminw ) {
30837			data.width = o.minWidth;
30838		}
30839		if ( isminh ) {
30840			data.height = o.minHeight;
30841		}
30842		if ( ismaxw ) {
30843			data.width = o.maxWidth;
30844		}
30845		if ( ismaxh ) {
30846			data.height = o.maxHeight;
30847		}
30848
30849		if ( isminw && cw ) {
30850			data.left = dw - o.minWidth;
30851		}
30852		if ( ismaxw && cw ) {
30853			data.left = dw - o.maxWidth;
30854		}
30855		if ( isminh && ch ) {
30856			data.top = dh - o.minHeight;
30857		}
30858		if ( ismaxh && ch ) {
30859			data.top = dh - o.maxHeight;
30860		}
30861
30862		// Fixing jump error on top/left - bug #2330
30863		if ( !data.width && !data.height && !data.left && data.top ) {
30864			data.top = null;
30865		} else if ( !data.width && !data.height && !data.top && data.left ) {
30866			data.left = null;
30867		}
30868
30869		return data;
30870	},
30871
30872	_getPaddingPlusBorderDimensions: function( element ) {
30873		var i = 0,
30874			widths = [],
30875			borders = [
30876				element.css( "borderTopWidth" ),
30877				element.css( "borderRightWidth" ),
30878				element.css( "borderBottomWidth" ),
30879				element.css( "borderLeftWidth" )
30880			],
30881			paddings = [
30882				element.css( "paddingTop" ),
30883				element.css( "paddingRight" ),
30884				element.css( "paddingBottom" ),
30885				element.css( "paddingLeft" )
30886			];
30887
30888		for ( ; i < 4; i++ ) {
30889			widths[ i ] = ( parseFloat( borders[ i ] ) || 0 );
30890			widths[ i ] += ( parseFloat( paddings[ i ] ) || 0 );
30891		}
30892
30893		return {
30894			height: widths[ 0 ] + widths[ 2 ],
30895			width: widths[ 1 ] + widths[ 3 ]
30896		};
30897	},
30898
30899	_proportionallyResize: function() {
30900
30901		if ( !this._proportionallyResizeElements.length ) {
30902			return;
30903		}
30904
30905		var prel,
30906			i = 0,
30907			element = this.helper || this.element;
30908
30909		for ( ; i < this._proportionallyResizeElements.length; i++ ) {
30910
30911			prel = this._proportionallyResizeElements[ i ];
30912
30913			// TODO: Seems like a bug to cache this.outerDimensions
30914			// considering that we are in a loop.
30915			if ( !this.outerDimensions ) {
30916				this.outerDimensions = this._getPaddingPlusBorderDimensions( prel );
30917			}
30918
30919			prel.css( {
30920				height: ( element.height() - this.outerDimensions.height ) || 0,
30921				width: ( element.width() - this.outerDimensions.width ) || 0
30922			} );
30923
30924		}
30925
30926	},
30927
30928	_renderProxy: function() {
30929
30930		var el = this.element, o = this.options;
30931		this.elementOffset = el.offset();
30932
30933		if ( this._helper ) {
30934
30935			this.helper = this.helper || $( "<div style='overflow:hidden;'></div>" );
30936
30937			this._addClass( this.helper, this._helper );
30938			this.helper.css( {
30939				width: this.element.outerWidth(),
30940				height: this.element.outerHeight(),
30941				position: "absolute",
30942				left: this.elementOffset.left + "px",
30943				top: this.elementOffset.top + "px",
30944				zIndex: ++o.zIndex //TODO: Don't modify option
30945			} );
30946
30947			this.helper
30948				.appendTo( "body" )
30949				.disableSelection();
30950
30951		} else {
30952			this.helper = this.element;
30953		}
30954
30955	},
30956
30957	_change: {
30958		e: function( event, dx ) {
30959			return { width: this.originalSize.width + dx };
30960		},
30961		w: function( event, dx ) {
30962			var cs = this.originalSize, sp = this.originalPosition;
30963			return { left: sp.left + dx, width: cs.width - dx };
30964		},
30965		n: function( event, dx, dy ) {
30966			var cs = this.originalSize, sp = this.originalPosition;
30967			return { top: sp.top + dy, height: cs.height - dy };
30968		},
30969		s: function( event, dx, dy ) {
30970			return { height: this.originalSize.height + dy };
30971		},
30972		se: function( event, dx, dy ) {
30973			return $.extend( this._change.s.apply( this, arguments ),
30974				this._change.e.apply( this, [ event, dx, dy ] ) );
30975		},
30976		sw: function( event, dx, dy ) {
30977			return $.extend( this._change.s.apply( this, arguments ),
30978				this._change.w.apply( this, [ event, dx, dy ] ) );
30979		},
30980		ne: function( event, dx, dy ) {
30981			return $.extend( this._change.n.apply( this, arguments ),
30982				this._change.e.apply( this, [ event, dx, dy ] ) );
30983		},
30984		nw: function( event, dx, dy ) {
30985			return $.extend( this._change.n.apply( this, arguments ),
30986				this._change.w.apply( this, [ event, dx, dy ] ) );
30987		}
30988	},
30989
30990	_propagate: function( n, event ) {
30991		$.ui.plugin.call( this, n, [ event, this.ui() ] );
30992		( n !== "resize" && this._trigger( n, event, this.ui() ) );
30993	},
30994
30995	plugins: {},
30996
30997	ui: function() {
30998		return {
30999			originalElement: this.originalElement,
31000			element: this.element,
31001			helper: this.helper,
31002			position: this.position,
31003			size: this.size,
31004			originalSize: this.originalSize,
31005			originalPosition: this.originalPosition
31006		};
31007	}
31008
31009} );
31010
31011/*
31012 * Resizable Extensions
31013 */
31014
31015$.ui.plugin.add( "resizable", "animate", {
31016
31017	stop: function( event ) {
31018		var that = $( this ).resizable( "instance" ),
31019			o = that.options,
31020			pr = that._proportionallyResizeElements,
31021			ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ),
31022			soffseth = ista && that._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height,
31023			soffsetw = ista ? 0 : that.sizeDiff.width,
31024			style = {
31025				width: ( that.size.width - soffsetw ),
31026				height: ( that.size.height - soffseth )
31027			},
31028			left = ( parseFloat( that.element.css( "left" ) ) +
31029				( that.position.left - that.originalPosition.left ) ) || null,
31030			top = ( parseFloat( that.element.css( "top" ) ) +
31031				( that.position.top - that.originalPosition.top ) ) || null;
31032
31033		that.element.animate(
31034			$.extend( style, top && left ? { top: top, left: left } : {} ), {
31035				duration: o.animateDuration,
31036				easing: o.animateEasing,
31037				step: function() {
31038
31039					var data = {
31040						width: parseFloat( that.element.css( "width" ) ),
31041						height: parseFloat( that.element.css( "height" ) ),
31042						top: parseFloat( that.element.css( "top" ) ),
31043						left: parseFloat( that.element.css( "left" ) )
31044					};
31045
31046					if ( pr && pr.length ) {
31047						$( pr[ 0 ] ).css( { width: data.width, height: data.height } );
31048					}
31049
31050					// Propagating resize, and updating values for each animation step
31051					that._updateCache( data );
31052					that._propagate( "resize", event );
31053
31054				}
31055			}
31056		);
31057	}
31058
31059} );
31060
31061$.ui.plugin.add( "resizable", "containment", {
31062
31063	start: function() {
31064		var element, p, co, ch, cw, width, height,
31065			that = $( this ).resizable( "instance" ),
31066			o = that.options,
31067			el = that.element,
31068			oc = o.containment,
31069			ce = ( oc instanceof $ ) ?
31070				oc.get( 0 ) :
31071				( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc;
31072
31073		if ( !ce ) {
31074			return;
31075		}
31076
31077		that.containerElement = $( ce );
31078
31079		if ( /document/.test( oc ) || oc === document ) {
31080			that.containerOffset = {
31081				left: 0,
31082				top: 0
31083			};
31084			that.containerPosition = {
31085				left: 0,
31086				top: 0
31087			};
31088
31089			that.parentData = {
31090				element: $( document ),
31091				left: 0,
31092				top: 0,
31093				width: $( document ).width(),
31094				height: $( document ).height() || document.body.parentNode.scrollHeight
31095			};
31096		} else {
31097			element = $( ce );
31098			p = [];
31099			$( [ "Top", "Right", "Left", "Bottom" ] ).each( function( i, name ) {
31100				p[ i ] = that._num( element.css( "padding" + name ) );
31101			} );
31102
31103			that.containerOffset = element.offset();
31104			that.containerPosition = element.position();
31105			that.containerSize = {
31106				height: ( element.innerHeight() - p[ 3 ] ),
31107				width: ( element.innerWidth() - p[ 1 ] )
31108			};
31109
31110			co = that.containerOffset;
31111			ch = that.containerSize.height;
31112			cw = that.containerSize.width;
31113			width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw );
31114			height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ;
31115
31116			that.parentData = {
31117				element: ce,
31118				left: co.left,
31119				top: co.top,
31120				width: width,
31121				height: height
31122			};
31123		}
31124	},
31125
31126	resize: function( event ) {
31127		var woset, hoset, isParent, isOffsetRelative,
31128			that = $( this ).resizable( "instance" ),
31129			o = that.options,
31130			co = that.containerOffset,
31131			cp = that.position,
31132			pRatio = that._aspectRatio || event.shiftKey,
31133			cop = {
31134				top: 0,
31135				left: 0
31136			},
31137			ce = that.containerElement,
31138			continueResize = true;
31139
31140		if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) {
31141			cop = co;
31142		}
31143
31144		if ( cp.left < ( that._helper ? co.left : 0 ) ) {
31145			that.size.width = that.size.width +
31146				( that._helper ?
31147					( that.position.left - co.left ) :
31148					( that.position.left - cop.left ) );
31149
31150			if ( pRatio ) {
31151				that.size.height = that.size.width / that.aspectRatio;
31152				continueResize = false;
31153			}
31154			that.position.left = o.helper ? co.left : 0;
31155		}
31156
31157		if ( cp.top < ( that._helper ? co.top : 0 ) ) {
31158			that.size.height = that.size.height +
31159				( that._helper ?
31160					( that.position.top - co.top ) :
31161					that.position.top );
31162
31163			if ( pRatio ) {
31164				that.size.width = that.size.height * that.aspectRatio;
31165				continueResize = false;
31166			}
31167			that.position.top = that._helper ? co.top : 0;
31168		}
31169
31170		isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );
31171		isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) );
31172
31173		if ( isParent && isOffsetRelative ) {
31174			that.offset.left = that.parentData.left + that.position.left;
31175			that.offset.top = that.parentData.top + that.position.top;
31176		} else {
31177			that.offset.left = that.element.offset().left;
31178			that.offset.top = that.element.offset().top;
31179		}
31180
31181		woset = Math.abs( that.sizeDiff.width +
31182			( that._helper ?
31183				that.offset.left - cop.left :
31184				( that.offset.left - co.left ) ) );
31185
31186		hoset = Math.abs( that.sizeDiff.height +
31187			( that._helper ?
31188				that.offset.top - cop.top :
31189				( that.offset.top - co.top ) ) );
31190
31191		if ( woset + that.size.width >= that.parentData.width ) {
31192			that.size.width = that.parentData.width - woset;
31193			if ( pRatio ) {
31194				that.size.height = that.size.width / that.aspectRatio;
31195				continueResize = false;
31196			}
31197		}
31198
31199		if ( hoset + that.size.height >= that.parentData.height ) {
31200			that.size.height = that.parentData.height - hoset;
31201			if ( pRatio ) {
31202				that.size.width = that.size.height * that.aspectRatio;
31203				continueResize = false;
31204			}
31205		}
31206
31207		if ( !continueResize ) {
31208			that.position.left = that.prevPosition.left;
31209			that.position.top = that.prevPosition.top;
31210			that.size.width = that.prevSize.width;
31211			that.size.height = that.prevSize.height;
31212		}
31213	},
31214
31215	stop: function() {
31216		var that = $( this ).resizable( "instance" ),
31217			o = that.options,
31218			co = that.containerOffset,
31219			cop = that.containerPosition,
31220			ce = that.containerElement,
31221			helper = $( that.helper ),
31222			ho = helper.offset(),
31223			w = helper.outerWidth() - that.sizeDiff.width,
31224			h = helper.outerHeight() - that.sizeDiff.height;
31225
31226		if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) {
31227			$( this ).css( {
31228				left: ho.left - cop.left - co.left,
31229				width: w,
31230				height: h
31231			} );
31232		}
31233
31234		if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) {
31235			$( this ).css( {
31236				left: ho.left - cop.left - co.left,
31237				width: w,
31238				height: h
31239			} );
31240		}
31241	}
31242} );
31243
31244$.ui.plugin.add( "resizable", "alsoResize", {
31245
31246	start: function() {
31247		var that = $( this ).resizable( "instance" ),
31248			o = that.options;
31249
31250		$( o.alsoResize ).each( function() {
31251			var el = $( this );
31252			el.data( "ui-resizable-alsoresize", {
31253				width: parseFloat( el.width() ), height: parseFloat( el.height() ),
31254				left: parseFloat( el.css( "left" ) ), top: parseFloat( el.css( "top" ) )
31255			} );
31256		} );
31257	},
31258
31259	resize: function( event, ui ) {
31260		var that = $( this ).resizable( "instance" ),
31261			o = that.options,
31262			os = that.originalSize,
31263			op = that.originalPosition,
31264			delta = {
31265				height: ( that.size.height - os.height ) || 0,
31266				width: ( that.size.width - os.width ) || 0,
31267				top: ( that.position.top - op.top ) || 0,
31268				left: ( that.position.left - op.left ) || 0
31269			};
31270
31271			$( o.alsoResize ).each( function() {
31272				var el = $( this ), start = $( this ).data( "ui-resizable-alsoresize" ), style = {},
31273					css = el.parents( ui.originalElement[ 0 ] ).length ?
31274							[ "width", "height" ] :
31275							[ "width", "height", "top", "left" ];
31276
31277				$.each( css, function( i, prop ) {
31278					var sum = ( start[ prop ] || 0 ) + ( delta[ prop ] || 0 );
31279					if ( sum && sum >= 0 ) {
31280						style[ prop ] = sum || null;
31281					}
31282				} );
31283
31284				el.css( style );
31285			} );
31286	},
31287
31288	stop: function() {
31289		$( this ).removeData( "ui-resizable-alsoresize" );
31290	}
31291} );
31292
31293$.ui.plugin.add( "resizable", "ghost", {
31294
31295	start: function() {
31296
31297		var that = $( this ).resizable( "instance" ), cs = that.size;
31298
31299		that.ghost = that.originalElement.clone();
31300		that.ghost.css( {
31301			opacity: 0.25,
31302			display: "block",
31303			position: "relative",
31304			height: cs.height,
31305			width: cs.width,
31306			margin: 0,
31307			left: 0,
31308			top: 0
31309		} );
31310
31311		that._addClass( that.ghost, "ui-resizable-ghost" );
31312
31313		// DEPRECATED
31314		// TODO: remove after 1.12
31315		if ( $.uiBackCompat !== false && typeof that.options.ghost === "string" ) {
31316
31317			// Ghost option
31318			that.ghost.addClass( this.options.ghost );
31319		}
31320
31321		that.ghost.appendTo( that.helper );
31322
31323	},
31324
31325	resize: function() {
31326		var that = $( this ).resizable( "instance" );
31327		if ( that.ghost ) {
31328			that.ghost.css( {
31329				position: "relative",
31330				height: that.size.height,
31331				width: that.size.width
31332			} );
31333		}
31334	},
31335
31336	stop: function() {
31337		var that = $( this ).resizable( "instance" );
31338		if ( that.ghost && that.helper ) {
31339			that.helper.get( 0 ).removeChild( that.ghost.get( 0 ) );
31340		}
31341	}
31342
31343} );
31344
31345$.ui.plugin.add( "resizable", "grid", {
31346
31347	resize: function() {
31348		var outerDimensions,
31349			that = $( this ).resizable( "instance" ),
31350			o = that.options,
31351			cs = that.size,
31352			os = that.originalSize,
31353			op = that.originalPosition,
31354			a = that.axis,
31355			grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid,
31356			gridX = ( grid[ 0 ] || 1 ),
31357			gridY = ( grid[ 1 ] || 1 ),
31358			ox = Math.round( ( cs.width - os.width ) / gridX ) * gridX,
31359			oy = Math.round( ( cs.height - os.height ) / gridY ) * gridY,
31360			newWidth = os.width + ox,
31361			newHeight = os.height + oy,
31362			isMaxWidth = o.maxWidth && ( o.maxWidth < newWidth ),
31363			isMaxHeight = o.maxHeight && ( o.maxHeight < newHeight ),
31364			isMinWidth = o.minWidth && ( o.minWidth > newWidth ),
31365			isMinHeight = o.minHeight && ( o.minHeight > newHeight );
31366
31367		o.grid = grid;
31368
31369		if ( isMinWidth ) {
31370			newWidth += gridX;
31371		}
31372		if ( isMinHeight ) {
31373			newHeight += gridY;
31374		}
31375		if ( isMaxWidth ) {
31376			newWidth -= gridX;
31377		}
31378		if ( isMaxHeight ) {
31379			newHeight -= gridY;
31380		}
31381
31382		if ( /^(se|s|e)$/.test( a ) ) {
31383			that.size.width = newWidth;
31384			that.size.height = newHeight;
31385		} else if ( /^(ne)$/.test( a ) ) {
31386			that.size.width = newWidth;
31387			that.size.height = newHeight;
31388			that.position.top = op.top - oy;
31389		} else if ( /^(sw)$/.test( a ) ) {
31390			that.size.width = newWidth;
31391			that.size.height = newHeight;
31392			that.position.left = op.left - ox;
31393		} else {
31394			if ( newHeight - gridY <= 0 || newWidth - gridX <= 0 ) {
31395				outerDimensions = that._getPaddingPlusBorderDimensions( this );
31396			}
31397
31398			if ( newHeight - gridY > 0 ) {
31399				that.size.height = newHeight;
31400				that.position.top = op.top - oy;
31401			} else {
31402				newHeight = gridY - outerDimensions.height;
31403				that.size.height = newHeight;
31404				that.position.top = op.top + os.height - newHeight;
31405			}
31406			if ( newWidth - gridX > 0 ) {
31407				that.size.width = newWidth;
31408				that.position.left = op.left - ox;
31409			} else {
31410				newWidth = gridX - outerDimensions.width;
31411				that.size.width = newWidth;
31412				that.position.left = op.left + os.width - newWidth;
31413			}
31414		}
31415	}
31416
31417} );
31418
31419return $.ui.resizable;
31420
31421} ) );
31422
31423
31424
31425
31426
31427
31428
31429
31430
31431
31432
31433
31434
31435
31436/*!
31437 * jQuery UI Dialog 1.12.1
31438 * http://jqueryui.com
31439 *
31440 * Copyright jQuery Foundation and other contributors
31441 * Released under the MIT license.
31442 * http://jquery.org/license
31443 */
31444
31445//>>label: Dialog
31446//>>group: Widgets
31447//>>description: Displays customizable dialog windows.
31448//>>docs: http://api.jqueryui.com/dialog/
31449//>>demos: http://jqueryui.com/dialog/
31450//>>css.structure: ../../themes/base/core.css
31451//>>css.structure: ../../themes/base/dialog.css
31452//>>css.theme: ../../themes/base/theme.css
31453
31454( function( factory ) {
31455	if ( typeof define === "function" && define.amd ) {
31456
31457		// AMD. Register as an anonymous module.
31458		define( [
31459			"jquery",
31460			"./button",
31461			"./draggable",
31462			"./mouse",
31463			"./resizable",
31464			"../focusable",
31465			"../keycode",
31466			"../position",
31467			"../safe-active-element",
31468			"../safe-blur",
31469			"../tabbable",
31470			"../unique-id",
31471			"../version",
31472			"../widget"
31473		], factory );
31474	} else {
31475
31476		// Browser globals
31477		factory( jQuery );
31478	}
31479}( function( $ ) {
vendor: 22,223 bytes, lines 31479-32372
31479
31480
31481$.widget( "ui.dialog", {
31482	version: "1.12.1",
31483	options: {
31484		appendTo: "body",
31485		autoOpen: true,
31486		buttons: [],
31487		classes: {
31488			"ui-dialog": "ui-corner-all",
31489			"ui-dialog-titlebar": "ui-corner-all"
31490		},
31491		closeOnEscape: true,
31492		closeText: "Close",
31493		draggable: true,
31494		hide: null,
31495		height: "auto",
31496		maxHeight: null,
31497		maxWidth: null,
31498		minHeight: 150,
31499		minWidth: 150,
31500		modal: false,
31501		position: {
31502			my: "center",
31503			at: "center",
31504			of: window,
31505			collision: "fit",
31506
31507			// Ensure the titlebar is always visible
31508			using: function( pos ) {
31509				var topOffset = $( this ).css( pos ).offset().top;
31510				if ( topOffset < 0 ) {
31511					$( this ).css( "top", pos.top - topOffset );
31512				}
31513			}
31514		},
31515		resizable: true,
31516		show: null,
31517		title: null,
31518		width: 300,
31519
31520		// Callbacks
31521		beforeClose: null,
31522		close: null,
31523		drag: null,
31524		dragStart: null,
31525		dragStop: null,
31526		focus: null,
31527		open: null,
31528		resize: null,
31529		resizeStart: null,
31530		resizeStop: null
31531	},
31532
31533	sizeRelatedOptions: {
31534		buttons: true,
31535		height: true,
31536		maxHeight: true,
31537		maxWidth: true,
31538		minHeight: true,
31539		minWidth: true,
31540		width: true
31541	},
31542
31543	resizableRelatedOptions: {
31544		maxHeight: true,
31545		maxWidth: true,
31546		minHeight: true,
31547		minWidth: true
31548	},
31549
31550	_create: function() {
31551		this.originalCss = {
31552			display: this.element[ 0 ].style.display,
31553			width: this.element[ 0 ].style.width,
31554			minHeight: this.element[ 0 ].style.minHeight,
31555			maxHeight: this.element[ 0 ].style.maxHeight,
31556			height: this.element[ 0 ].style.height
31557		};
31558		this.originalPosition = {
31559			parent: this.element.parent(),
31560			index: this.element.parent().children().index( this.element )
31561		};
31562		this.originalTitle = this.element.attr( "title" );
31563		if ( this.options.title == null && this.originalTitle != null ) {
31564			this.options.title = this.originalTitle;
31565		}
31566
31567		// Dialogs can't be disabled
31568		if ( this.options.disabled ) {
31569			this.options.disabled = false;
31570		}
31571
31572		this._createWrapper();
31573
31574		this.element
31575			.show()
31576			.removeAttr( "title" )
31577			.appendTo( this.uiDialog );
31578
31579		this._addClass( "ui-dialog-content", "ui-widget-content" );
31580
31581		this._createTitlebar();
31582		this._createButtonPane();
31583
31584		if ( this.options.draggable && $.fn.draggable ) {
31585			this._makeDraggable();
31586		}
31587		if ( this.options.resizable && $.fn.resizable ) {
31588			this._makeResizable();
31589		}
31590
31591		this._isOpen = false;
31592
31593		this._trackFocus();
31594	},
31595
31596	_init: function() {
31597		if ( this.options.autoOpen ) {
31598			this.open();
31599		}
31600	},
31601
31602	_appendTo: function() {
31603		var element = this.options.appendTo;
31604		if ( element && ( element.jquery || element.nodeType ) ) {
31605			return $( element );
31606		}
31607		return this.document.find( element || "body" ).eq( 0 );
31608	},
31609
31610	_destroy: function() {
31611		var next,
31612			originalPosition = this.originalPosition;
31613
31614		this._untrackInstance();
31615		this._destroyOverlay();
31616
31617		this.element
31618			.removeUniqueId()
31619			.css( this.originalCss )
31620
31621			// Without detaching first, the following becomes really slow
31622			.detach();
31623
31624		this.uiDialog.remove();
31625
31626		if ( this.originalTitle ) {
31627			this.element.attr( "title", this.originalTitle );
31628		}
31629
31630		next = originalPosition.parent.children().eq( originalPosition.index );
31631
31632		// Don't try to place the dialog next to itself (#8613)
31633		if ( next.length && next[ 0 ] !== this.element[ 0 ] ) {
31634			next.before( this.element );
31635		} else {
31636			originalPosition.parent.append( this.element );
31637		}
31638	},
31639
31640	widget: function() {
31641		return this.uiDialog;
31642	},
31643
31644	disable: $.noop,
31645	enable: $.noop,
31646
31647	close: function( event ) {
31648		var that = this;
31649
31650		if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) {
31651			return;
31652		}
31653
31654		this._isOpen = false;
31655		this._focusedElement = null;
31656		this._destroyOverlay();
31657		this._untrackInstance();
31658
31659		if ( !this.opener.filter( ":focusable" ).trigger( "focus" ).length ) {
31660
31661			// Hiding a focused element doesn't trigger blur in WebKit
31662			// so in case we have nothing to focus on, explicitly blur the active element
31663			// https://bugs.webkit.org/show_bug.cgi?id=47182
31664			$.ui.safeBlur( $.ui.safeActiveElement( this.document[ 0 ] ) );
31665		}
31666
31667		this._hide( this.uiDialog, this.options.hide, function() {
31668			that._trigger( "close", event );
31669		} );
31670	},
31671
31672	isOpen: function() {
31673		return this._isOpen;
31674	},
31675
31676	moveToTop: function() {
31677		this._moveToTop();
31678	},
31679
31680	_moveToTop: function( event, silent ) {
31681		var moved = false,
31682			zIndices = this.uiDialog.siblings( ".ui-front:visible" ).map( function() {
31683				return +$( this ).css( "z-index" );
31684			} ).get(),
31685			zIndexMax = Math.max.apply( null, zIndices );
31686
31687		if ( zIndexMax >= +this.uiDialog.css( "z-index" ) ) {
31688			this.uiDialog.css( "z-index", zIndexMax + 1 );
31689			moved = true;
31690		}
31691
31692		if ( moved && !silent ) {
31693			this._trigger( "focus", event );
31694		}
31695		return moved;
31696	},
31697
31698	open: function() {
31699		var that = this;
31700		if ( this._isOpen ) {
31701			if ( this._moveToTop() ) {
31702				this._focusTabbable();
31703			}
31704			return;
31705		}
31706
31707		this._isOpen = true;
31708		this.opener = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
31709
31710		this._size();
31711		this._position();
31712		this._createOverlay();
31713		this._moveToTop( null, true );
31714
31715		// Ensure the overlay is moved to the top with the dialog, but only when
31716		// opening. The overlay shouldn't move after the dialog is open so that
31717		// modeless dialogs opened after the modal dialog stack properly.
31718		if ( this.overlay ) {
31719			this.overlay.css( "z-index", this.uiDialog.css( "z-index" ) - 1 );
31720		}
31721
31722		this._show( this.uiDialog, this.options.show, function() {
31723			that._focusTabbable();
31724			that._trigger( "focus" );
31725		} );
31726
31727		// Track the dialog immediately upon openening in case a focus event
31728		// somehow occurs outside of the dialog before an element inside the
31729		// dialog is focused (#10152)
31730		this._makeFocusTarget();
31731
31732		this._trigger( "open" );
31733	},
31734
31735	_focusTabbable: function() {
31736
31737		// Set focus to the first match:
31738		// 1. An element that was focused previously
31739		// 2. First element inside the dialog matching [autofocus]
31740		// 3. Tabbable element inside the content element
31741		// 4. Tabbable element inside the buttonpane
31742		// 5. The close button
31743		// 6. The dialog itself
31744		var hasFocus = this._focusedElement;
31745		if ( !hasFocus ) {
31746			hasFocus = this.element.find( "[autofocus]" );
31747		}
31748		if ( !hasFocus.length ) {
31749			hasFocus = this.element.find( ":tabbable" );
31750		}
31751		if ( !hasFocus.length ) {
31752			hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
31753		}
31754		if ( !hasFocus.length ) {
31755			hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" );
31756		}
31757		if ( !hasFocus.length ) {
31758			hasFocus = this.uiDialog;
31759		}
31760		hasFocus.eq( 0 ).trigger( "focus" );
31761	},
31762
31763	_keepFocus: function( event ) {
31764		function checkFocus() {
31765			var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
31766				isActive = this.uiDialog[ 0 ] === activeElement ||
31767					$.contains( this.uiDialog[ 0 ], activeElement );
31768			if ( !isActive ) {
31769				this._focusTabbable();
31770			}
31771		}
31772		event.preventDefault();
31773		checkFocus.call( this );
31774
31775		// support: IE
31776		// IE <= 8 doesn't prevent moving focus even with event.preventDefault()
31777		// so we check again later
31778		this._delay( checkFocus );
31779	},
31780
31781	_createWrapper: function() {
31782		this.uiDialog = $( "<div>" )
31783			.hide()
31784			.attr( {
31785
31786				// Setting tabIndex makes the div focusable
31787				tabIndex: -1,
31788				role: "dialog"
31789			} )
31790			.appendTo( this._appendTo() );
31791
31792		this._addClass( this.uiDialog, "ui-dialog", "ui-widget ui-widget-content ui-front" );
31793		this._on( this.uiDialog, {
31794			keydown: function( event ) {
31795				if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
31796						event.keyCode === $.ui.keyCode.ESCAPE ) {
31797					event.preventDefault();
31798					this.close( event );
31799					return;
31800				}
31801
31802				// Prevent tabbing out of dialogs
31803				if ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) {
31804					return;
31805				}
31806				var tabbables = this.uiDialog.find( ":tabbable" ),
31807					first = tabbables.filter( ":first" ),
31808					last = tabbables.filter( ":last" );
31809
31810				if ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) &&
31811						!event.shiftKey ) {
31812					this._delay( function() {
31813						first.trigger( "focus" );
31814					} );
31815					event.preventDefault();
31816				} else if ( ( event.target === first[ 0 ] ||
31817						event.target === this.uiDialog[ 0 ] ) && event.shiftKey ) {
31818					this._delay( function() {
31819						last.trigger( "focus" );
31820					} );
31821					event.preventDefault();
31822				}
31823			},
31824			mousedown: function( event ) {
31825				if ( this._moveToTop( event ) ) {
31826					this._focusTabbable();
31827				}
31828			}
31829		} );
31830
31831		// We assume that any existing aria-describedby attribute means
31832		// that the dialog content is marked up properly
31833		// otherwise we brute force the content as the description
31834		if ( !this.element.find( "[aria-describedby]" ).length ) {
31835			this.uiDialog.attr( {
31836				"aria-describedby": this.element.uniqueId().attr( "id" )
31837			} );
31838		}
31839	},
31840
31841	_createTitlebar: function() {
31842		var uiDialogTitle;
31843
31844		this.uiDialogTitlebar = $( "<div>" );
31845		this._addClass( this.uiDialogTitlebar,
31846			"ui-dialog-titlebar", "ui-widget-header ui-helper-clearfix" );
31847		this._on( this.uiDialogTitlebar, {
31848			mousedown: function( event ) {
31849
31850				// Don't prevent click on close button (#8838)
31851				// Focusing a dialog that is partially scrolled out of view
31852				// causes the browser to scroll it into view, preventing the click event
31853				if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) {
31854
31855					// Dialog isn't getting focus when dragging (#8063)
31856					this.uiDialog.trigger( "focus" );
31857				}
31858			}
31859		} );
31860
31861		// Support: IE
31862		// Use type="button" to prevent enter keypresses in textboxes from closing the
31863		// dialog in IE (#9312)
31864		this.uiDialogTitlebarClose = $( "<button type='button'></button>" )
31865			.button( {
31866				label: $( "<a>" ).text( this.options.closeText ).html(),
31867				icon: "ui-icon-closethick",
31868				showLabel: false
31869			} )
31870			.appendTo( this.uiDialogTitlebar );
31871
31872		this._addClass( this.uiDialogTitlebarClose, "ui-dialog-titlebar-close" );
31873		this._on( this.uiDialogTitlebarClose, {
31874			click: function( event ) {
31875				event.preventDefault();
31876				this.close( event );
31877			}
31878		} );
31879
31880		uiDialogTitle = $( "<span>" ).uniqueId().prependTo( this.uiDialogTitlebar );
31881		this._addClass( uiDialogTitle, "ui-dialog-title" );
31882		this._title( uiDialogTitle );
31883
31884		this.uiDialogTitlebar.prependTo( this.uiDialog );
31885
31886		this.uiDialog.attr( {
31887			"aria-labelledby": uiDialogTitle.attr( "id" )
31888		} );
31889	},
31890
31891	_title: function( title ) {
31892		if ( this.options.title ) {
31893			title.text( this.options.title );
31894		} else {
31895			title.html( "&#160;" );
31896		}
31897	},
31898
31899	_createButtonPane: function() {
31900		this.uiDialogButtonPane = $( "<div>" );
31901		this._addClass( this.uiDialogButtonPane, "ui-dialog-buttonpane",
31902			"ui-widget-content ui-helper-clearfix" );
31903
31904		this.uiButtonSet = $( "<div>" )
31905			.appendTo( this.uiDialogButtonPane );
31906		this._addClass( this.uiButtonSet, "ui-dialog-buttonset" );
31907
31908		this._createButtons();
31909	},
31910
31911	_createButtons: function() {
31912		var that = this,
31913			buttons = this.options.buttons;
31914
31915		// If we already have a button pane, remove it
31916		this.uiDialogButtonPane.remove();
31917		this.uiButtonSet.empty();
31918
31919		if ( $.isEmptyObject( buttons ) || ( $.isArray( buttons ) && !buttons.length ) ) {
31920			this._removeClass( this.uiDialog, "ui-dialog-buttons" );
31921			return;
31922		}
31923
31924		$.each( buttons, function( name, props ) {
31925			var click, buttonOptions;
31926			props = $.isFunction( props ) ?
31927				{ click: props, text: name } :
31928				props;
31929
31930			// Default to a non-submitting button
31931			props = $.extend( { type: "button" }, props );
31932
31933			// Change the context for the click callback to be the main element
31934			click = props.click;
31935			buttonOptions = {
31936				icon: props.icon,
31937				iconPosition: props.iconPosition,
31938				showLabel: props.showLabel,
31939
31940				// Deprecated options
31941				icons: props.icons,
31942				text: props.text
31943			};
31944
31945			delete props.click;
31946			delete props.icon;
31947			delete props.iconPosition;
31948			delete props.showLabel;
31949
31950			// Deprecated options
31951			delete props.icons;
31952			if ( typeof props.text === "boolean" ) {
31953				delete props.text;
31954			}
31955
31956			$( "<button></button>", props )
31957				.button( buttonOptions )
31958				.appendTo( that.uiButtonSet )
31959				.on( "click", function() {
31960					click.apply( that.element[ 0 ], arguments );
31961				} );
31962		} );
31963		this._addClass( this.uiDialog, "ui-dialog-buttons" );
31964		this.uiDialogButtonPane.appendTo( this.uiDialog );
31965	},
31966
31967	_makeDraggable: function() {
31968		var that = this,
31969			options = this.options;
31970
31971		function filteredUi( ui ) {
31972			return {
31973				position: ui.position,
31974				offset: ui.offset
31975			};
31976		}
31977
31978		this.uiDialog.draggable( {
31979			cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
31980			handle: ".ui-dialog-titlebar",
31981			containment: "document",
31982			start: function( event, ui ) {
31983				that._addClass( $( this ), "ui-dialog-dragging" );
31984				that._blockFrames();
31985				that._trigger( "dragStart", event, filteredUi( ui ) );
31986			},
31987			drag: function( event, ui ) {
31988				that._trigger( "drag", event, filteredUi( ui ) );
31989			},
31990			stop: function( event, ui ) {
31991				var left = ui.offset.left - that.document.scrollLeft(),
31992					top = ui.offset.top - that.document.scrollTop();
31993
31994				options.position = {
31995					my: "left top",
31996					at: "left" + ( left >= 0 ? "+" : "" ) + left + " " +
31997						"top" + ( top >= 0 ? "+" : "" ) + top,
31998					of: that.window
31999				};
32000				that._removeClass( $( this ), "ui-dialog-dragging" );
32001				that._unblockFrames();
32002				that._trigger( "dragStop", event, filteredUi( ui ) );
32003			}
32004		} );
32005	},
32006
32007	_makeResizable: function() {
32008		var that = this,
32009			options = this.options,
32010			handles = options.resizable,
32011
32012			// .ui-resizable has position: relative defined in the stylesheet
32013			// but dialogs have to use absolute or fixed positioning
32014			position = this.uiDialog.css( "position" ),
32015			resizeHandles = typeof handles === "string" ?
32016				handles :
32017				"n,e,s,w,se,sw,ne,nw";
32018
32019		function filteredUi( ui ) {
32020			return {
32021				originalPosition: ui.originalPosition,
32022				originalSize: ui.originalSize,
32023				position: ui.position,
32024				size: ui.size
32025			};
32026		}
32027
32028		this.uiDialog.resizable( {
32029			cancel: ".ui-dialog-content",
32030			containment: "document",
32031			alsoResize: this.element,
32032			maxWidth: options.maxWidth,
32033			maxHeight: options.maxHeight,
32034			minWidth: options.minWidth,
32035			minHeight: this._minHeight(),
32036			handles: resizeHandles,
32037			start: function( event, ui ) {
32038				that._addClass( $( this ), "ui-dialog-resizing" );
32039				that._blockFrames();
32040				that._trigger( "resizeStart", event, filteredUi( ui ) );
32041			},
32042			resize: function( event, ui ) {
32043				that._trigger( "resize", event, filteredUi( ui ) );
32044			},
32045			stop: function( event, ui ) {
32046				var offset = that.uiDialog.offset(),
32047					left = offset.left - that.document.scrollLeft(),
32048					top = offset.top - that.document.scrollTop();
32049
32050				options.height = that.uiDialog.height();
32051				options.width = that.uiDialog.width();
32052				options.position = {
32053					my: "left top",
32054					at: "left" + ( left >= 0 ? "+" : "" ) + left + " " +
32055						"top" + ( top >= 0 ? "+" : "" ) + top,
32056					of: that.window
32057				};
32058				that._removeClass( $( this ), "ui-dialog-resizing" );
32059				that._unblockFrames();
32060				that._trigger( "resizeStop", event, filteredUi( ui ) );
32061			}
32062		} )
32063			.css( "position", position );
32064	},
32065
32066	_trackFocus: function() {
32067		this._on( this.widget(), {
32068			focusin: function( event ) {
32069				this._makeFocusTarget();
32070				this._focusedElement = $( event.target );
32071			}
32072		} );
32073	},
32074
32075	_makeFocusTarget: function() {
32076		this._untrackInstance();
32077		this._trackingInstances().unshift( this );
32078	},
32079
32080	_untrackInstance: function() {
32081		var instances = this._trackingInstances(),
32082			exists = $.inArray( this, instances );
32083		if ( exists !== -1 ) {
32084			instances.splice( exists, 1 );
32085		}
32086	},
32087
32088	_trackingInstances: function() {
32089		var instances = this.document.data( "ui-dialog-instances" );
32090		if ( !instances ) {
32091			instances = [];
32092			this.document.data( "ui-dialog-instances", instances );
32093		}
32094		return instances;
32095	},
32096
32097	_minHeight: function() {
32098		var options = this.options;
32099
32100		return options.height === "auto" ?
32101			options.minHeight :
32102			Math.min( options.minHeight, options.height );
32103	},
32104
32105	_position: function() {
32106
32107		// Need to show the dialog to get the actual offset in the position plugin
32108		var isVisible = this.uiDialog.is( ":visible" );
32109		if ( !isVisible ) {
32110			this.uiDialog.show();
32111		}
32112		this.uiDialog.position( this.options.position );
32113		if ( !isVisible ) {
32114			this.uiDialog.hide();
32115		}
32116	},
32117
32118	_setOptions: function( options ) {
32119		var that = this,
32120			resize = false,
32121			resizableOptions = {};
32122
32123		$.each( options, function( key, value ) {
32124			that._setOption( key, value );
32125
32126			if ( key in that.sizeRelatedOptions ) {
32127				resize = true;
32128			}
32129			if ( key in that.resizableRelatedOptions ) {
32130				resizableOptions[ key ] = value;
32131			}
32132		} );
32133
32134		if ( resize ) {
32135			this._size();
32136			this._position();
32137		}
32138		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
32139			this.uiDialog.resizable( "option", resizableOptions );
32140		}
32141	},
32142
32143	_setOption: function( key, value ) {
32144		var isDraggable, isResizable,
32145			uiDialog = this.uiDialog;
32146
32147		if ( key === "disabled" ) {
32148			return;
32149		}
32150
32151		this._super( key, value );
32152
32153		if ( key === "appendTo" ) {
32154			this.uiDialog.appendTo( this._appendTo() );
32155		}
32156
32157		if ( key === "buttons" ) {
32158			this._createButtons();
32159		}
32160
32161		if ( key === "closeText" ) {
32162			this.uiDialogTitlebarClose.button( {
32163
32164				// Ensure that we always pass a string
32165				label: $( "<a>" ).text( "" + this.options.closeText ).html()
32166			} );
32167		}
32168
32169		if ( key === "draggable" ) {
32170			isDraggable = uiDialog.is( ":data(ui-draggable)" );
32171			if ( isDraggable && !value ) {
32172				uiDialog.draggable( "destroy" );
32173			}
32174
32175			if ( !isDraggable && value ) {
32176				this._makeDraggable();
32177			}
32178		}
32179
32180		if ( key === "position" ) {
32181			this._position();
32182		}
32183
32184		if ( key === "resizable" ) {
32185
32186			// currently resizable, becoming non-resizable
32187			isResizable = uiDialog.is( ":data(ui-resizable)" );
32188			if ( isResizable && !value ) {
32189				uiDialog.resizable( "destroy" );
32190			}
32191
32192			// Currently resizable, changing handles
32193			if ( isResizable && typeof value === "string" ) {
32194				uiDialog.resizable( "option", "handles", value );
32195			}
32196
32197			// Currently non-resizable, becoming resizable
32198			if ( !isResizable && value !== false ) {
32199				this._makeResizable();
32200			}
32201		}
32202
32203		if ( key === "title" ) {
32204			this._title( this.uiDialogTitlebar.find( ".ui-dialog-title" ) );
32205		}
32206	},
32207
32208	_size: function() {
32209
32210		// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
32211		// divs will both have width and height set, so we need to reset them
32212		var nonContentHeight, minContentHeight, maxContentHeight,
32213			options = this.options;
32214
32215		// Reset content sizing
32216		this.element.show().css( {
32217			width: "auto",
32218			minHeight: 0,
32219			maxHeight: "none",
32220			height: 0
32221		} );
32222
32223		if ( options.minWidth > options.width ) {
32224			options.width = options.minWidth;
32225		}
32226
32227		// Reset wrapper sizing
32228		// determine the height of all the non-content elements
32229		nonContentHeight = this.uiDialog.css( {
32230			height: "auto",
32231			width: options.width
32232		} )
32233			.outerHeight();
32234		minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
32235		maxContentHeight = typeof options.maxHeight === "number" ?
32236			Math.max( 0, options.maxHeight - nonContentHeight ) :
32237			"none";
32238
32239		if ( options.height === "auto" ) {
32240			this.element.css( {
32241				minHeight: minContentHeight,
32242				maxHeight: maxContentHeight,
32243				height: "auto"
32244			} );
32245		} else {
32246			this.element.height( Math.max( 0, options.height - nonContentHeight ) );
32247		}
32248
32249		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
32250			this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
32251		}
32252	},
32253
32254	_blockFrames: function() {
32255		this.iframeBlocks = this.document.find( "iframe" ).map( function() {
32256			var iframe = $( this );
32257
32258			return $( "<div>" )
32259				.css( {
32260					position: "absolute",
32261					width: iframe.outerWidth(),
32262					height: iframe.outerHeight()
32263				} )
32264				.appendTo( iframe.parent() )
32265				.offset( iframe.offset() )[ 0 ];
32266		} );
32267	},
32268
32269	_unblockFrames: function() {
32270		if ( this.iframeBlocks ) {
32271			this.iframeBlocks.remove();
32272			delete this.iframeBlocks;
32273		}
32274	},
32275
32276	_allowInteraction: function( event ) {
32277		if ( $( event.target ).closest( ".ui-dialog" ).length ) {
32278			return true;
32279		}
32280
32281		// TODO: Remove hack when datepicker implements
32282		// the .ui-front logic (#8989)
32283		return !!$( event.target ).closest( ".ui-datepicker" ).length;
32284	},
32285
32286	_createOverlay: function() {
32287		if ( !this.options.modal ) {
32288			return;
32289		}
32290
32291		// We use a delay in case the overlay is created from an
32292		// event that we're going to be cancelling (#2804)
32293		var isOpening = true;
32294		this._delay( function() {
32295			isOpening = false;
32296		} );
32297
32298		if ( !this.document.data( "ui-dialog-overlays" ) ) {
32299
32300			// Prevent use of anchors and inputs
32301			// Using _on() for an event handler shared across many instances is
32302			// safe because the dialogs stack and must be closed in reverse order
32303			this._on( this.document, {
32304				focusin: function( event ) {
32305					if ( isOpening ) {
32306						return;
32307					}
32308
32309					if ( !this._allowInteraction( event ) ) {
32310						event.preventDefault();
32311						this._trackingInstances()[ 0 ]._focusTabbable();
32312					}
32313				}
32314			} );
32315		}
32316
32317		this.overlay = $( "<div>" )
32318			.appendTo( this._appendTo() );
32319
32320		this._addClass( this.overlay, null, "ui-widget-overlay ui-front" );
32321		this._on( this.overlay, {
32322			mousedown: "_keepFocus"
32323		} );
32324		this.document.data( "ui-dialog-overlays",
32325			( this.document.data( "ui-dialog-overlays" ) || 0 ) + 1 );
32326	},
32327
32328	_destroyOverlay: function() {
32329		if ( !this.options.modal ) {
32330			return;
32331		}
32332
32333		if ( this.overlay ) {
32334			var overlays = this.document.data( "ui-dialog-overlays" ) - 1;
32335
32336			if ( !overlays ) {
32337				this._off( this.document, "focusin" );
32338				this.document.removeData( "ui-dialog-overlays" );
32339			} else {
32340				this.document.data( "ui-dialog-overlays", overlays );
32341			}
32342
32343			this.overlay.remove();
32344			this.overlay = null;
32345		}
32346	}
32347} );
32348
32349// DEPRECATED
32350// TODO: switch return back to widget declaration at top of file when this is removed
32351if ( $.uiBackCompat !== false ) {
32352
32353	// Backcompat for dialogClass option
32354	$.widget( "ui.dialog", $.ui.dialog, {
32355		options: {
32356			dialogClass: ""
32357		},
32358		_createWrapper: function() {
32359			this._super();
32360			this.uiDialog.addClass( this.options.dialogClass );
32361		},
32362		_setOption: function( key, value ) {
32363			if ( key === "dialogClass" ) {
32364				this.uiDialog
32365					.removeClass( this.options.dialogClass )
32366					.addClass( value );
32367			}
32368			this._superApply( arguments );
32369		}
32370	} );
32371}
32372
32373return $.ui.dialog;
32374
32375} ) );
32376
32377
32378
32379
32380
32381/*!
32382 * jQuery UI Droppable 1.12.1
32383 * http://jqueryui.com
32384 *
32385 * Copyright jQuery Foundation and other contributors
32386 * Released under the MIT license.
32387 * http://jquery.org/license
32388 */
32389
32390//>>label: Droppable
32391//>>group: Interactions
32392//>>description: Enables drop targets for draggable elements.
32393//>>docs: http://api.jqueryui.com/droppable/
32394//>>demos: http://jqueryui.com/droppable/
32395
32396( function( factory ) {
32397	if ( typeof define === "function" && define.amd ) {
32398
32399		// AMD. Register as an anonymous module.
32400		define( [
32401			"jquery",
32402			"./draggable",
32403			"./mouse",
32404			"../version",
32405			"../widget"
32406		], factory );
32407	} else {
32408
32409		// Browser globals
32410		factory( jQuery );
32411	}
32412}( function( $ ) {
vendor: 12,128 bytes, lines 32412-32874
32412
32413
32414$.widget( "ui.droppable", {
32415	version: "1.12.1",
32416	widgetEventPrefix: "drop",
32417	options: {
32418		accept: "*",
32419		addClasses: true,
32420		greedy: false,
32421		scope: "default",
32422		tolerance: "intersect",
32423
32424		// Callbacks
32425		activate: null,
32426		deactivate: null,
32427		drop: null,
32428		out: null,
32429		over: null
32430	},
32431	_create: function() {
32432
32433		var proportions,
32434			o = this.options,
32435			accept = o.accept;
32436
32437		this.isover = false;
32438		this.isout = true;
32439
32440		this.accept = $.isFunction( accept ) ? accept : function( d ) {
32441			return d.is( accept );
32442		};
32443
32444		this.proportions = function( /* valueToWrite */ ) {
32445			if ( arguments.length ) {
32446
32447				// Store the droppable's proportions
32448				proportions = arguments[ 0 ];
32449			} else {
32450
32451				// Retrieve or derive the droppable's proportions
32452				return proportions ?
32453					proportions :
32454					proportions = {
32455						width: this.element[ 0 ].offsetWidth,
32456						height: this.element[ 0 ].offsetHeight
32457					};
32458			}
32459		};
32460
32461		this._addToManager( o.scope );
32462
32463		o.addClasses && this._addClass( "ui-droppable" );
32464
32465	},
32466
32467	_addToManager: function( scope ) {
32468
32469		// Add the reference and positions to the manager
32470		$.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];
32471		$.ui.ddmanager.droppables[ scope ].push( this );
32472	},
32473
32474	_splice: function( drop ) {
32475		var i = 0;
32476		for ( ; i < drop.length; i++ ) {
32477			if ( drop[ i ] === this ) {
32478				drop.splice( i, 1 );
32479			}
32480		}
32481	},
32482
32483	_destroy: function() {
32484		var drop = $.ui.ddmanager.droppables[ this.options.scope ];
32485
32486		this._splice( drop );
32487	},
32488
32489	_setOption: function( key, value ) {
32490
32491		if ( key === "accept" ) {
32492			this.accept = $.isFunction( value ) ? value : function( d ) {
32493				return d.is( value );
32494			};
32495		} else if ( key === "scope" ) {
32496			var drop = $.ui.ddmanager.droppables[ this.options.scope ];
32497
32498			this._splice( drop );
32499			this._addToManager( value );
32500		}
32501
32502		this._super( key, value );
32503	},
32504
32505	_activate: function( event ) {
32506		var draggable = $.ui.ddmanager.current;
32507
32508		this._addActiveClass();
32509		if ( draggable ) {
32510			this._trigger( "activate", event, this.ui( draggable ) );
32511		}
32512	},
32513
32514	_deactivate: function( event ) {
32515		var draggable = $.ui.ddmanager.current;
32516
32517		this._removeActiveClass();
32518		if ( draggable ) {
32519			this._trigger( "deactivate", event, this.ui( draggable ) );
32520		}
32521	},
32522
32523	_over: function( event ) {
32524
32525		var draggable = $.ui.ddmanager.current;
32526
32527		// Bail if draggable and droppable are same element
32528		if ( !draggable || ( draggable.currentItem ||
32529				draggable.element )[ 0 ] === this.element[ 0 ] ) {
32530			return;
32531		}
32532
32533		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||
32534				draggable.element ) ) ) {
32535			this._addHoverClass();
32536			this._trigger( "over", event, this.ui( draggable ) );
32537		}
32538
32539	},
32540
32541	_out: function( event ) {
32542
32543		var draggable = $.ui.ddmanager.current;
32544
32545		// Bail if draggable and droppable are same element
32546		if ( !draggable || ( draggable.currentItem ||
32547				draggable.element )[ 0 ] === this.element[ 0 ] ) {
32548			return;
32549		}
32550
32551		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||
32552				draggable.element ) ) ) {
32553			this._removeHoverClass();
32554			this._trigger( "out", event, this.ui( draggable ) );
32555		}
32556
32557	},
32558
32559	_drop: function( event, custom ) {
32560
32561		var draggable = custom || $.ui.ddmanager.current,
32562			childrenIntersection = false;
32563
32564		// Bail if draggable and droppable are same element
32565		if ( !draggable || ( draggable.currentItem ||
32566				draggable.element )[ 0 ] === this.element[ 0 ] ) {
32567			return false;
32568		}
32569
32570		this.element
32571			.find( ":data(ui-droppable)" )
32572			.not( ".ui-draggable-dragging" )
32573			.each( function() {
32574				var inst = $( this ).droppable( "instance" );
32575				if (
32576					inst.options.greedy &&
32577					!inst.options.disabled &&
32578					inst.options.scope === draggable.options.scope &&
32579					inst.accept.call(
32580						inst.element[ 0 ], ( draggable.currentItem || draggable.element )
32581					) &&
32582					intersect(
32583						draggable,
32584						$.extend( inst, { offset: inst.element.offset() } ),
32585						inst.options.tolerance, event
32586					)
32587				) {
32588					childrenIntersection = true;
32589					return false; }
32590			} );
32591		if ( childrenIntersection ) {
32592			return false;
32593		}
32594
32595		if ( this.accept.call( this.element[ 0 ],
32596				( draggable.currentItem || draggable.element ) ) ) {
32597			this._removeActiveClass();
32598			this._removeHoverClass();
32599
32600			this._trigger( "drop", event, this.ui( draggable ) );
32601			return this.element;
32602		}
32603
32604		return false;
32605
32606	},
32607
32608	ui: function( c ) {
32609		return {
32610			draggable: ( c.currentItem || c.element ),
32611			helper: c.helper,
32612			position: c.position,
32613			offset: c.positionAbs
32614		};
32615	},
32616
32617	// Extension points just to make backcompat sane and avoid duplicating logic
32618	// TODO: Remove in 1.13 along with call to it below
32619	_addHoverClass: function() {
32620		this._addClass( "ui-droppable-hover" );
32621	},
32622
32623	_removeHoverClass: function() {
32624		this._removeClass( "ui-droppable-hover" );
32625	},
32626
32627	_addActiveClass: function() {
32628		this._addClass( "ui-droppable-active" );
32629	},
32630
32631	_removeActiveClass: function() {
32632		this._removeClass( "ui-droppable-active" );
32633	}
32634} );
32635
32636var intersect = $.ui.intersect = ( function() {
32637	function isOverAxis( x, reference, size ) {
32638		return ( x >= reference ) && ( x < ( reference + size ) );
32639	}
32640
32641	return function( draggable, droppable, toleranceMode, event ) {
32642
32643		if ( !droppable.offset ) {
32644			return false;
32645		}
32646
32647		var x1 = ( draggable.positionAbs ||
32648				draggable.position.absolute ).left + draggable.margins.left,
32649			y1 = ( draggable.positionAbs ||
32650				draggable.position.absolute ).top + draggable.margins.top,
32651			x2 = x1 + draggable.helperProportions.width,
32652			y2 = y1 + draggable.helperProportions.height,
32653			l = droppable.offset.left,
32654			t = droppable.offset.top,
32655			r = l + droppable.proportions().width,
32656			b = t + droppable.proportions().height;
32657
32658		switch ( toleranceMode ) {
32659		case "fit":
32660			return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
32661		case "intersect":
32662			return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
32663				x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
32664				t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
32665				y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
32666		case "pointer":
32667			return isOverAxis( event.pageY, t, droppable.proportions().height ) &&
32668				isOverAxis( event.pageX, l, droppable.proportions().width );
32669		case "touch":
32670			return (
32671				( y1 >= t && y1 <= b ) || // Top edge touching
32672				( y2 >= t && y2 <= b ) || // Bottom edge touching
32673				( y1 < t && y2 > b ) // Surrounded vertically
32674			) && (
32675				( x1 >= l && x1 <= r ) || // Left edge touching
32676				( x2 >= l && x2 <= r ) || // Right edge touching
32677				( x1 < l && x2 > r ) // Surrounded horizontally
32678			);
32679		default:
32680			return false;
32681		}
32682	};
32683} )();
32684
32685/*
32686	This manager tracks offsets of draggables and droppables
32687*/
32688$.ui.ddmanager = {
32689	current: null,
32690	droppables: { "default": [] },
32691	prepareOffsets: function( t, event ) {
32692
32693		var i, j,
32694			m = $.ui.ddmanager.droppables[ t.options.scope ] || [],
32695			type = event ? event.type : null, // workaround for #2317
32696			list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack();
32697
32698		droppablesLoop: for ( i = 0; i < m.length; i++ ) {
32699
32700			// No disabled and non-accepted
32701			if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ],
32702					( t.currentItem || t.element ) ) ) ) {
32703				continue;
32704			}
32705
32706			// Filter out elements in the current dragged item
32707			for ( j = 0; j < list.length; j++ ) {
32708				if ( list[ j ] === m[ i ].element[ 0 ] ) {
32709					m[ i ].proportions().height = 0;
32710					continue droppablesLoop;
32711				}
32712			}
32713
32714			m[ i ].visible = m[ i ].element.css( "display" ) !== "none";
32715			if ( !m[ i ].visible ) {
32716				continue;
32717			}
32718
32719			// Activate the droppable if used directly from draggables
32720			if ( type === "mousedown" ) {
32721				m[ i ]._activate.call( m[ i ], event );
32722			}
32723
32724			m[ i ].offset = m[ i ].element.offset();
32725			m[ i ].proportions( {
32726				width: m[ i ].element[ 0 ].offsetWidth,
32727				height: m[ i ].element[ 0 ].offsetHeight
32728			} );
32729
32730		}
32731
32732	},
32733	drop: function( draggable, event ) {
32734
32735		var dropped = false;
32736
32737		// Create a copy of the droppables in case the list changes during the drop (#9116)
32738		$.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {
32739
32740			if ( !this.options ) {
32741				return;
32742			}
32743			if ( !this.options.disabled && this.visible &&
32744					intersect( draggable, this, this.options.tolerance, event ) ) {
32745				dropped = this._drop.call( this, event ) || dropped;
32746			}
32747
32748			if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ],
32749					( draggable.currentItem || draggable.element ) ) ) {
32750				this.isout = true;
32751				this.isover = false;
32752				this._deactivate.call( this, event );
32753			}
32754
32755		} );
32756		return dropped;
32757
32758	},
32759	dragStart: function( draggable, event ) {
32760
32761		// Listen for scrolling so that if the dragging causes scrolling the position of the
32762		// droppables can be recalculated (see #5003)
32763		draggable.element.parentsUntil( "body" ).on( "scroll.droppable", function() {
32764			if ( !draggable.options.refreshPositions ) {
32765				$.ui.ddmanager.prepareOffsets( draggable, event );
32766			}
32767		} );
32768	},
32769	drag: function( draggable, event ) {
32770
32771		// If you have a highly dynamic page, you might try this option. It renders positions
32772		// every time you move the mouse.
32773		if ( draggable.options.refreshPositions ) {
32774			$.ui.ddmanager.prepareOffsets( draggable, event );
32775		}
32776
32777		// Run through all droppables and check their positions based on specific tolerance options
32778		$.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {
32779
32780			if ( this.options.disabled || this.greedyChild || !this.visible ) {
32781				return;
32782			}
32783
32784			var parentInstance, scope, parent,
32785				intersects = intersect( draggable, this, this.options.tolerance, event ),
32786				c = !intersects && this.isover ?
32787					"isout" :
32788					( intersects && !this.isover ? "isover" : null );
32789			if ( !c ) {
32790				return;
32791			}
32792
32793			if ( this.options.greedy ) {
32794
32795				// find droppable parents with same scope
32796				scope = this.options.scope;
32797				parent = this.element.parents( ":data(ui-droppable)" ).filter( function() {
32798					return $( this ).droppable( "instance" ).options.scope === scope;
32799				} );
32800
32801				if ( parent.length ) {
32802					parentInstance = $( parent[ 0 ] ).droppable( "instance" );
32803					parentInstance.greedyChild = ( c === "isover" );
32804				}
32805			}
32806
32807			// We just moved into a greedy child
32808			if ( parentInstance && c === "isover" ) {
32809				parentInstance.isover = false;
32810				parentInstance.isout = true;
32811				parentInstance._out.call( parentInstance, event );
32812			}
32813
32814			this[ c ] = true;
32815			this[ c === "isout" ? "isover" : "isout" ] = false;
32816			this[ c === "isover" ? "_over" : "_out" ].call( this, event );
32817
32818			// We just moved out of a greedy child
32819			if ( parentInstance && c === "isout" ) {
32820				parentInstance.isout = false;
32821				parentInstance.isover = true;
32822				parentInstance._over.call( parentInstance, event );
32823			}
32824		} );
32825
32826	},
32827	dragStop: function( draggable, event ) {
32828		draggable.element.parentsUntil( "body" ).off( "scroll.droppable" );
32829
32830		// Call prepareOffsets one final time since IE does not fire return scroll events when
32831		// overflow was caused by drag (see #5003)
32832		if ( !draggable.options.refreshPositions ) {
32833			$.ui.ddmanager.prepareOffsets( draggable, event );
32834		}
32835	}
32836};
32837
32838// DEPRECATED
32839// TODO: switch return back to widget declaration at top of file when this is removed
32840if ( $.uiBackCompat !== false ) {
32841
32842	// Backcompat for activeClass and hoverClass options
32843	$.widget( "ui.droppable", $.ui.droppable, {
32844		options: {
32845			hoverClass: false,
32846			activeClass: false
32847		},
32848		_addActiveClass: function() {
32849			this._super();
32850			if ( this.options.activeClass ) {
32851				this.element.addClass( this.options.activeClass );
32852			}
32853		},
32854		_removeActiveClass: function() {
32855			this._super();
32856			if ( this.options.activeClass ) {
32857				this.element.removeClass( this.options.activeClass );
32858			}
32859		},
32860		_addHoverClass: function() {
32861			this._super();
32862			if ( this.options.hoverClass ) {
32863				this.element.addClass( this.options.hoverClass );
32864			}
32865		},
32866		_removeHoverClass: function() {
32867			this._super();
32868			if ( this.options.hoverClass ) {
32869				this.element.removeClass( this.options.hoverClass );
32870			}
32871		}
32872	} );
32873}
32874
32875return $.ui.droppable;
32876
32877} ) );
32878
32879
32880
32881/*!
32882 * jQuery UI Progressbar 1.12.1
32883 * http://jqueryui.com
32884 *
32885 * Copyright jQuery Foundation and other contributors
32886 * Released under the MIT license.
32887 * http://jquery.org/license
32888 */
32889
32890//>>label: Progressbar
32891//>>group: Widgets
32892// jscs:disable maximumLineLength
32893//>>
vendor: 4,690 bytes, lines 32893-33096
32893description: Displays a status indicator for loading state, standard percentage, and other progress indicators.
32894// jscs:enable maximumLineLength
32895//>>docs: http://api.jqueryui.com/progressbar/
32896//>>demos: http://jqueryui.com/progressbar/
32897//>>css.structure: ../../themes/base/core.css
32898//>>css.structure: ../../themes/base/progressbar.css
32899//>>css.theme: ../../themes/base/theme.css
32900
32901( function( factory ) {
32902	if ( typeof define === "function" && define.amd ) {
32903
32904		// AMD. Register as an anonymous module.
32905		define( [
32906			"jquery",
32907			"../version",
32908			"../widget"
32909		], factory );
32910	} else {
32911
32912		// Browser globals
32913		factory( jQuery );
32914	}
32915}( function( $ ) {
32916
32917return $.widget( "ui.progressbar", {
32918	version: "1.12.1",
32919	options: {
32920		classes: {
32921			"ui-progressbar": "ui-corner-all",
32922			"ui-progressbar-value": "ui-corner-left",
32923			"ui-progressbar-complete": "ui-corner-right"
32924		},
32925		max: 100,
32926		value: 0,
32927
32928		change: null,
32929		complete: null
32930	},
32931
32932	min: 0,
32933
32934	_create: function() {
32935
32936		// Constrain initial value
32937		this.oldValue = this.options.value = this._constrainedValue();
32938
32939		this.element.attr( {
32940
32941			// Only set static values; aria-valuenow and aria-valuemax are
32942			// set inside _refreshValue()
32943			role: "progressbar",
32944			"aria-valuemin": this.min
32945		} );
32946		this._addClass( "ui-progressbar", "ui-widget ui-widget-content" );
32947
32948		this.valueDiv = $( "<div>" ).appendTo( this.element );
32949		this._addClass( this.valueDiv, "ui-progressbar-value", "ui-widget-header" );
32950		this._refreshValue();
32951	},
32952
32953	_destroy: function() {
32954		this.element.removeAttr( "role aria-valuemin aria-valuemax aria-valuenow" );
32955
32956		this.valueDiv.remove();
32957	},
32958
32959	value: function( newValue ) {
32960		if ( newValue === undefined ) {
32961			return this.options.value;
32962		}
32963
32964		this.options.value = this._constrainedValue( newValue );
32965		this._refreshValue();
32966	},
32967
32968	_constrainedValue: function( newValue ) {
32969		if ( newValue === undefined ) {
32970			newValue = this.options.value;
32971		}
32972
32973		this.indeterminate = newValue === false;
32974
32975		// Sanitize value
32976		if ( typeof newValue !== "number" ) {
32977			newValue = 0;
32978		}
32979
32980		return this.indeterminate ? false :
32981			Math.min( this.options.max, Math.max( this.min, newValue ) );
32982	},
32983
32984	_setOptions: function( options ) {
32985
32986		// Ensure "value" option is set after other values (like max)
32987		var value = options.value;
32988		delete options.value;
32989
32990		this._super( options );
32991
32992		this.options.value = this._constrainedValue( value );
32993		this._refreshValue();
32994	},
32995
32996	_setOption: function( key, value ) {
32997		if ( key === "max" ) {
32998
32999			// Don't allow a max less than min
33000			value = Math.max( this.min, value );
33001		}
33002		this._super( key, value );
33003	},
33004
33005	_setOptionDisabled: function( value ) {
33006		this._super( value );
33007
33008		this.element.attr( "aria-disabled", value );
33009		this._toggleClass( null, "ui-state-disabled", !!value );
33010	},
33011
33012	_percentage: function() {
33013		return this.indeterminate ?
33014			100 :
33015			100 * ( this.options.value - this.min ) / ( this.options.max - this.min );
33016	},
33017
33018	_refreshValue: function() {
33019		var value = this.options.value,
33020			percentage = this._percentage();
33021
33022		this.valueDiv
33023			.toggle( this.indeterminate || value > this.min )
33024			.width( percentage.toFixed( 0 ) + "%" );
33025
33026		this
33027			._toggleClass( this.valueDiv, "ui-progressbar-complete", null,
33028				value === this.options.max )
33029			._toggleClass( "ui-progressbar-indeterminate", null, this.indeterminate );
33030
33031		if ( this.indeterminate ) {
33032			this.element.removeAttr( "aria-valuenow" );
33033			if ( !this.overlayDiv ) {
33034				this.overlayDiv = $( "<div>" ).appendTo( this.valueDiv );
33035				this._addClass( this.overlayDiv, "ui-progressbar-overlay" );
33036			}
33037		} else {
33038			this.element.attr( {
33039				"aria-valuemax": this.options.max,
33040				"aria-valuenow": value
33041			} );
33042			if ( this.overlayDiv ) {
33043				this.overlayDiv.remove();
33044				this.overlayDiv = null;
33045			}
33046		}
33047
33048		if ( this.oldValue !== value ) {
33049			this.oldValue = value;
33050			this._trigger( "change" );
33051		}
33052		if ( value === this.options.max ) {
33053			this._trigger( "complete" );
33054		}
33055	}
33056} );
33057
33058} ) );
33059
33060
33061
33062
33063/*!
33064 * jQuery UI Selectable 1.12.1
33065 * http://jqueryui.com
33066 *
33067 * Copyright jQuery Foundation and other contributors
33068 * Released under the MIT license.
33069 * http://jquery.org/license
33070 */
33071
33072//>>label: Selectable
33073//>>group: Interactions
33074//>>description: Allows groups of elements to be selected with the mouse.
33075//>>docs: http://api.jqueryui.com/selectable/
33076//>>demos: http://jqueryui.com/selectable/
33077//>>css.structure: ../../themes/base/selectable.css
33078
33079( function( factory ) {
33080	if ( typeof define === "function" && define.amd ) {
33081
33082		// AMD. Register as an anonymous module.
33083		define( [
33084			"jquery",
33085			"./mouse",
33086			"../version",
33087			"../widget"
33088		], factory );
33089	} else {
33090
33091		// Browser globals
33092		factory( jQuery );
33093	}
33094}( function( $ ) {
33095
33096return
vendor: 7,342 bytes, lines 33096-33371
33096 $.widget( "ui.selectable", $.ui.mouse, {
33097	version: "1.12.1",
33098	options: {
33099		appendTo: "body",
33100		autoRefresh: true,
33101		distance: 0,
33102		filter: "*",
33103		tolerance: "touch",
33104
33105		// Callbacks
33106		selected: null,
33107		selecting: null,
33108		start: null,
33109		stop: null,
33110		unselected: null,
33111		unselecting: null
33112	},
33113	_create: function() {
33114		var that = this;
33115
33116		this._addClass( "ui-selectable" );
33117
33118		this.dragged = false;
33119
33120		// Cache selectee children based on filter
33121		this.refresh = function() {
33122			that.elementPos = $( that.element[ 0 ] ).offset();
33123			that.selectees = $( that.options.filter, that.element[ 0 ] );
33124			that._addClass( that.selectees, "ui-selectee" );
33125			that.selectees.each( function() {
33126				var $this = $( this ),
33127					selecteeOffset = $this.offset(),
33128					pos = {
33129						left: selecteeOffset.left - that.elementPos.left,
33130						top: selecteeOffset.top - that.elementPos.top
33131					};
33132				$.data( this, "selectable-item", {
33133					element: this,
33134					$element: $this,
33135					left: pos.left,
33136					top: pos.top,
33137					right: pos.left + $this.outerWidth(),
33138					bottom: pos.top + $this.outerHeight(),
33139					startselected: false,
33140					selected: $this.hasClass( "ui-selected" ),
33141					selecting: $this.hasClass( "ui-selecting" ),
33142					unselecting: $this.hasClass( "ui-unselecting" )
33143				} );
33144			} );
33145		};
33146		this.refresh();
33147
33148		this._mouseInit();
33149
33150		this.helper = $( "<div>" );
33151		this._addClass( this.helper, "ui-selectable-helper" );
33152	},
33153
33154	_destroy: function() {
33155		this.selectees.removeData( "selectable-item" );
33156		this._mouseDestroy();
33157	},
33158
33159	_mouseStart: function( event ) {
33160		var that = this,
33161			options = this.options;
33162
33163		this.opos = [ event.pageX, event.pageY ];
33164		this.elementPos = $( this.element[ 0 ] ).offset();
33165
33166		if ( this.options.disabled ) {
33167			return;
33168		}
33169
33170		this.selectees = $( options.filter, this.element[ 0 ] );
33171
33172		this._trigger( "start", event );
33173
33174		$( options.appendTo ).append( this.helper );
33175
33176		// position helper (lasso)
33177		this.helper.css( {
33178			"left": event.pageX,
33179			"top": event.pageY,
33180			"width": 0,
33181			"height": 0
33182		} );
33183
33184		if ( options.autoRefresh ) {
33185			this.refresh();
33186		}
33187
33188		this.selectees.filter( ".ui-selected" ).each( function() {
33189			var selectee = $.data( this, "selectable-item" );
33190			selectee.startselected = true;
33191			if ( !event.metaKey && !event.ctrlKey ) {
33192				that._removeClass( selectee.$element, "ui-selected" );
33193				selectee.selected = false;
33194				that._addClass( selectee.$element, "ui-unselecting" );
33195				selectee.unselecting = true;
33196
33197				// selectable UNSELECTING callback
33198				that._trigger( "unselecting", event, {
33199					unselecting: selectee.element
33200				} );
33201			}
33202		} );
33203
33204		$( event.target ).parents().addBack().each( function() {
33205			var doSelect,
33206				selectee = $.data( this, "selectable-item" );
33207			if ( selectee ) {
33208				doSelect = ( !event.metaKey && !event.ctrlKey ) ||
33209					!selectee.$element.hasClass( "ui-selected" );
33210				that._removeClass( selectee.$element, doSelect ? "ui-unselecting" : "ui-selected" )
33211					._addClass( selectee.$element, doSelect ? "ui-selecting" : "ui-unselecting" );
33212				selectee.unselecting = !doSelect;
33213				selectee.selecting = doSelect;
33214				selectee.selected = doSelect;
33215
33216				// selectable (UN)SELECTING callback
33217				if ( doSelect ) {
33218					that._trigger( "selecting", event, {
33219						selecting: selectee.element
33220					} );
33221				} else {
33222					that._trigger( "unselecting", event, {
33223						unselecting: selectee.element
33224					} );
33225				}
33226				return false;
33227			}
33228		} );
33229
33230	},
33231
33232	_mouseDrag: function( event ) {
33233
33234		this.dragged = true;
33235
33236		if ( this.options.disabled ) {
33237			return;
33238		}
33239
33240		var tmp,
33241			that = this,
33242			options = this.options,
33243			x1 = this.opos[ 0 ],
33244			y1 = this.opos[ 1 ],
33245			x2 = event.pageX,
33246			y2 = event.pageY;
33247
33248		if ( x1 > x2 ) { tmp = x2; x2 = x1; x1 = tmp; }
33249		if ( y1 > y2 ) { tmp = y2; y2 = y1; y1 = tmp; }
33250		this.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } );
33251
33252		this.selectees.each( function() {
33253			var selectee = $.data( this, "selectable-item" ),
33254				hit = false,
33255				offset = {};
33256
33257			//prevent helper from being selected if appendTo: selectable
33258			if ( !selectee || selectee.element === that.element[ 0 ] ) {
33259				return;
33260			}
33261
33262			offset.left   = selectee.left   + that.elementPos.left;
33263			offset.right  = selectee.right  + that.elementPos.left;
33264			offset.top    = selectee.top    + that.elementPos.top;
33265			offset.bottom = selectee.bottom + that.elementPos.top;
33266
33267			if ( options.tolerance === "touch" ) {
33268				hit = ( !( offset.left > x2 || offset.right < x1 || offset.top > y2 ||
33269                    offset.bottom < y1 ) );
33270			} else if ( options.tolerance === "fit" ) {
33271				hit = ( offset.left > x1 && offset.right < x2 && offset.top > y1 &&
33272                    offset.bottom < y2 );
33273			}
33274
33275			if ( hit ) {
33276
33277				// SELECT
33278				if ( selectee.selected ) {
33279					that._removeClass( selectee.$element, "ui-selected" );
33280					selectee.selected = false;
33281				}
33282				if ( selectee.unselecting ) {
33283					that._removeClass( selectee.$element, "ui-unselecting" );
33284					selectee.unselecting = false;
33285				}
33286				if ( !selectee.selecting ) {
33287					that._addClass( selectee.$element, "ui-selecting" );
33288					selectee.selecting = true;
33289
33290					// selectable SELECTING callback
33291					that._trigger( "selecting", event, {
33292						selecting: selectee.element
33293					} );
33294				}
33295			} else {
33296
33297				// UNSELECT
33298				if ( selectee.selecting ) {
33299					if ( ( event.metaKey || event.ctrlKey ) && selectee.startselected ) {
33300						that._removeClass( selectee.$element, "ui-selecting" );
33301						selectee.selecting = false;
33302						that._addClass( selectee.$element, "ui-selected" );
33303						selectee.selected = true;
33304					} else {
33305						that._removeClass( selectee.$element, "ui-selecting" );
33306						selectee.selecting = false;
33307						if ( selectee.startselected ) {
33308							that._addClass( selectee.$element, "ui-unselecting" );
33309							selectee.unselecting = true;
33310						}
33311
33312						// selectable UNSELECTING callback
33313						that._trigger( "unselecting", event, {
33314							unselecting: selectee.element
33315						} );
33316					}
33317				}
33318				if ( selectee.selected ) {
33319					if ( !event.metaKey && !event.ctrlKey && !selectee.startselected ) {
33320						that._removeClass( selectee.$element, "ui-selected" );
33321						selectee.selected = false;
33322
33323						that._addClass( selectee.$element, "ui-unselecting" );
33324						selectee.unselecting = true;
33325
33326						// selectable UNSELECTING callback
33327						that._trigger( "unselecting", event, {
33328							unselecting: selectee.element
33329						} );
33330					}
33331				}
33332			}
33333		} );
33334
33335		return false;
33336	},
33337
33338	_mouseStop: function( event ) {
33339		var that = this;
33340
33341		this.dragged = false;
33342
33343		$( ".ui-unselecting", this.element[ 0 ] ).each( function() {
33344			var selectee = $.data( this, "selectable-item" );
33345			that._removeClass( selectee.$element, "ui-unselecting" );
33346			selectee.unselecting = false;
33347			selectee.startselected = false;
33348			that._trigger( "unselected", event, {
33349				unselected: selectee.element
33350			} );
33351		} );
33352		$( ".ui-selecting", this.element[ 0 ] ).each( function() {
33353			var selectee = $.data( this, "selectable-item" );
33354			that._removeClass( selectee.$element, "ui-selecting" )
33355				._addClass( selectee.$element, "ui-selected" );
33356			selectee.selecting = false;
33357			selectee.selected = true;
33358			selectee.startselected = true;
33359			that._trigger( "selected", event, {
33360				selected: selectee.element
33361			} );
33362		} );
33363		this._trigger( "stop", event );
33364
33365		this.helper.remove();
33366
33367		return false;
33368	}
33369
33370} );
33371
33372} ) );
33373
33374
33375
33376
33377
33378
33379
33380
33381
33382
33383/*!
33384 * jQuery UI Selectmenu 1.12.1
33385 * http://jqueryui.com
33386 *
33387 * Copyright jQuery Foundation and other contributors
33388 * Released under the MIT license.
33389 * http://jquery.org/license
33390 */
33391
33392//>>label: Selectmenu
33393//>>group: Widgets
33394// jscs:disable maximumLineLength
33395//>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select.
33396// jscs:enable maximumLineLength
33397//>>docs: http://api.jqueryui.com/selectmenu/
33398//>>demos: http://jqueryui.com/selectmenu/
33399//>>css.structure: ../../themes/base/core.css
33400//>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css
33401//>>css.theme: ../../themes/base/theme.css
33402
33403( function( factory ) {
33404	if ( typeof define === "function" && define.amd ) {
33405
33406		// AMD. Register as an anonymous module.
33407		define( [
33408			"jquery",
33409			"./menu",
33410			"../escape-selector",
33411			"../form-reset-mixin",
33412			"../keycode",
33413			"../labels",
33414			"../position",
33415			"../unique-id",
33416			"../version",
33417			"../widget"
33418		], factory );
33419	} else {
33420
33421		// Browser globals
33422		factory( jQuery );
33423	}
33424}( function( $ ) {
33425
33426return
vendor: 15,028 bytes, lines 33426-34068
33426 $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
33427	version: "1.12.1",
33428	defaultElement: "<select>",
33429	options: {
33430		appendTo: null,
33431		classes: {
33432			"ui-selectmenu-button-open": "ui-corner-top",
33433			"ui-selectmenu-button-closed": "ui-corner-all"
33434		},
33435		disabled: null,
33436		icons: {
33437			button: "ui-icon-triangle-1-s"
33438		},
33439		position: {
33440			my: "left top",
33441			at: "left bottom",
33442			collision: "none"
33443		},
33444		width: false,
33445
33446		// Callbacks
33447		change: null,
33448		close: null,
33449		focus: null,
33450		open: null,
33451		select: null
33452	},
33453
33454	_create: function() {
33455		var selectmenuId = this.element.uniqueId().attr( "id" );
33456		this.ids = {
33457			element: selectmenuId,
33458			button: selectmenuId + "-button",
33459			menu: selectmenuId + "-menu"
33460		};
33461
33462		this._drawButton();
33463		this._drawMenu();
33464		this._bindFormResetHandler();
33465
33466		this._rendered = false;
33467		this.menuItems = $();
33468	},
33469
33470	_drawButton: function() {
33471		var icon,
33472			that = this,
33473			item = this._parseOption(
33474				this.element.find( "option:selected" ),
33475				this.element[ 0 ].selectedIndex
33476			);
33477
33478		// Associate existing label with the new button
33479		this.labels = this.element.labels().attr( "for", this.ids.button );
33480		this._on( this.labels, {
33481			click: function( event ) {
33482				this.button.focus();
33483				event.preventDefault();
33484			}
33485		} );
33486
33487		// Hide original select element
33488		this.element.hide();
33489
33490		// Create button
33491		this.button = $( "<span>", {
33492			tabindex: this.options.disabled ? -1 : 0,
33493			id: this.ids.button,
33494			role: "combobox",
33495			"aria-expanded": "false",
33496			"aria-autocomplete": "list",
33497			"aria-owns": this.ids.menu,
33498			"aria-haspopup": "true",
33499			title: this.element.attr( "title" )
33500		} )
33501			.insertAfter( this.element );
33502
33503		this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed",
33504			"ui-button ui-widget" );
33505
33506		icon = $( "<span>" ).appendTo( this.button );
33507		this._addClass( icon, "ui-selectmenu-icon", "ui-icon " + this.options.icons.button );
33508		this.buttonItem = this._renderButtonItem( item )
33509			.appendTo( this.button );
33510
33511		if ( this.options.width !== false ) {
33512			this._resizeButton();
33513		}
33514
33515		this._on( this.button, this._buttonEvents );
33516		this.button.one( "focusin", function() {
33517
33518			// Delay rendering the menu items until the button receives focus.
33519			// The menu may have already been rendered via a programmatic open.
33520			if ( !that._rendered ) {
33521				that._refreshMenu();
33522			}
33523		} );
33524	},
33525
33526	_drawMenu: function() {
33527		var that = this;
33528
33529		// Create menu
33530		this.menu = $( "<ul>", {
33531			"aria-hidden": "true",
33532			"aria-labelledby": this.ids.button,
33533			id: this.ids.menu
33534		} );
33535
33536		// Wrap menu
33537		this.menuWrap = $( "<div>" ).append( this.menu );
33538		this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" );
33539		this.menuWrap.appendTo( this._appendTo() );
33540
33541		// Initialize menu widget
33542		this.menuInstance = this.menu
33543			.menu( {
33544				classes: {
33545					"ui-menu": "ui-corner-bottom"
33546				},
33547				role: "listbox",
33548				select: function( event, ui ) {
33549					event.preventDefault();
33550
33551					// Support: IE8
33552					// If the item was selected via a click, the text selection
33553					// will be destroyed in IE
33554					that._setSelection();
33555
33556					that._select( ui.item.data( "ui-selectmenu-item" ), event );
33557				},
33558				focus: function( event, ui ) {
33559					var item = ui.item.data( "ui-selectmenu-item" );
33560
33561					// Prevent inital focus from firing and check if its a newly focused item
33562					if ( that.focusIndex != null && item.index !== that.focusIndex ) {
33563						that._trigger( "focus", event, { item: item } );
33564						if ( !that.isOpen ) {
33565							that._select( item, event );
33566						}
33567					}
33568					that.focusIndex = item.index;
33569
33570					that.button.attr( "aria-activedescendant",
33571						that.menuItems.eq( item.index ).attr( "id" ) );
33572				}
33573			} )
33574			.menu( "instance" );
33575
33576		// Don't close the menu on mouseleave
33577		this.menuInstance._off( this.menu, "mouseleave" );
33578
33579		// Cancel the menu's collapseAll on document click
33580		this.menuInstance._closeOnDocumentClick = function() {
33581			return false;
33582		};
33583
33584		// Selects often contain empty items, but never contain dividers
33585		this.menuInstance._isDivider = function() {
33586			return false;
33587		};
33588	},
33589
33590	refresh: function() {
33591		this._refreshMenu();
33592		this.buttonItem.replaceWith(
33593			this.buttonItem = this._renderButtonItem(
33594
33595				// Fall back to an empty object in case there are no options
33596				this._getSelectedItem().data( "ui-selectmenu-item" ) || {}
33597			)
33598		);
33599		if ( this.options.width === null ) {
33600			this._resizeButton();
33601		}
33602	},
33603
33604	_refreshMenu: function() {
33605		var item,
33606			options = this.element.find( "option" );
33607
33608		this.menu.empty();
33609
33610		this._parseOptions( options );
33611		this._renderMenu( this.menu, this.items );
33612
33613		this.menuInstance.refresh();
33614		this.menuItems = this.menu.find( "li" )
33615			.not( ".ui-selectmenu-optgroup" )
33616				.find( ".ui-menu-item-wrapper" );
33617
33618		this._rendered = true;
33619
33620		if ( !options.length ) {
33621			return;
33622		}
33623
33624		item = this._getSelectedItem();
33625
33626		// Update the menu to have the correct item focused
33627		this.menuInstance.focus( null, item );
33628		this._setAria( item.data( "ui-selectmenu-item" ) );
33629
33630		// Set disabled state
33631		this._setOption( "disabled", this.element.prop( "disabled" ) );
33632	},
33633
33634	open: function( event ) {
33635		if ( this.options.disabled ) {
33636			return;
33637		}
33638
33639		// If this is the first time the menu is being opened, render the items
33640		if ( !this._rendered ) {
33641			this._refreshMenu();
33642		} else {
33643
33644			// Menu clears focus on close, reset focus to selected item
33645			this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" );
33646			this.menuInstance.focus( null, this._getSelectedItem() );
33647		}
33648
33649		// If there are no options, don't open the menu
33650		if ( !this.menuItems.length ) {
33651			return;
33652		}
33653
33654		this.isOpen = true;
33655		this._toggleAttr();
33656		this._resizeMenu();
33657		this._position();
33658
33659		this._on( this.document, this._documentClick );
33660
33661		this._trigger( "open", event );
33662	},
33663
33664	_position: function() {
33665		this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );
33666	},
33667
33668	close: function( event ) {
33669		if ( !this.isOpen ) {
33670			return;
33671		}
33672
33673		this.isOpen = false;
33674		this._toggleAttr();
33675
33676		this.range = null;
33677		this._off( this.document );
33678
33679		this._trigger( "close", event );
33680	},
33681
33682	widget: function() {
33683		return this.button;
33684	},
33685
33686	menuWidget: function() {
33687		return this.menu;
33688	},
33689
33690	_renderButtonItem: function( item ) {
33691		var buttonItem = $( "<span>" );
33692
33693		this._setText( buttonItem, item.label );
33694		this._addClass( buttonItem, "ui-selectmenu-text" );
33695
33696		return buttonItem;
33697	},
33698
33699	_renderMenu: function( ul, items ) {
33700		var that = this,
33701			currentOptgroup = "";
33702
33703		$.each( items, function( index, item ) {
33704			var li;
33705
33706			if ( item.optgroup !== currentOptgroup ) {
33707				li = $( "<li>", {
33708					text: item.optgroup
33709				} );
33710				that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" +
33711					( item.element.parent( "optgroup" ).prop( "disabled" ) ?
33712						" ui-state-disabled" :
33713						"" ) );
33714
33715				li.appendTo( ul );
33716
33717				currentOptgroup = item.optgroup;
33718			}
33719
33720			that._renderItemData( ul, item );
33721		} );
33722	},
33723
33724	_renderItemData: function( ul, item ) {
33725		return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
33726	},
33727
33728	_renderItem: function( ul, item ) {
33729		var li = $( "<li>" ),
33730			wrapper = $( "<div>", {
33731				title: item.element.attr( "title" )
33732			} );
33733
33734		if ( item.disabled ) {
33735			this._addClass( li, null, "ui-state-disabled" );
33736		}
33737		this._setText( wrapper, item.label );
33738
33739		return li.append( wrapper ).appendTo( ul );
33740	},
33741
33742	_setText: function( element, value ) {
33743		if ( value ) {
33744			element.text( value );
33745		} else {
33746			element.html( "&#160;" );
33747		}
33748	},
33749
33750	_move: function( direction, event ) {
33751		var item, next,
33752			filter = ".ui-menu-item";
33753
33754		if ( this.isOpen ) {
33755			item = this.menuItems.eq( this.focusIndex ).parent( "li" );
33756		} else {
33757			item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
33758			filter += ":not(.ui-state-disabled)";
33759		}
33760
33761		if ( direction === "first" || direction === "last" ) {
33762			next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 );
33763		} else {
33764			next = item[ direction + "All" ]( filter ).eq( 0 );
33765		}
33766
33767		if ( next.length ) {
33768			this.menuInstance.focus( event, next );
33769		}
33770	},
33771
33772	_getSelectedItem: function() {
33773		return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
33774	},
33775
33776	_toggle: function( event ) {
33777		this[ this.isOpen ? "close" : "open" ]( event );
33778	},
33779
33780	_setSelection: function() {
33781		var selection;
33782
33783		if ( !this.range ) {
33784			return;
33785		}
33786
33787		if ( window.getSelection ) {
33788			selection = window.getSelection();
33789			selection.removeAllRanges();
33790			selection.addRange( this.range );
33791
33792		// Support: IE8
33793		} else {
33794			this.range.select();
33795		}
33796
33797		// Support: IE
33798		// Setting the text selection kills the button focus in IE, but
33799		// restoring the focus doesn't kill the selection.
33800		this.button.focus();
33801	},
33802
33803	_documentClick: {
33804		mousedown: function( event ) {
33805			if ( !this.isOpen ) {
33806				return;
33807			}
33808
33809			if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" +
33810					$.ui.escapeSelector( this.ids.button ) ).length ) {
33811				this.close( event );
33812			}
33813		}
33814	},
33815
33816	_buttonEvents: {
33817
33818		// Prevent text selection from being reset when interacting with the selectmenu (#10144)
33819		mousedown: function() {
33820			var selection;
33821
33822			if ( window.getSelection ) {
33823				selection = window.getSelection();
33824				if ( selection.rangeCount ) {
33825					this.range = selection.getRangeAt( 0 );
33826				}
33827
33828			// Support: IE8
33829			} else {
33830				this.range = document.selection.createRange();
33831			}
33832		},
33833
33834		click: function( event ) {
33835			this._setSelection();
33836			this._toggle( event );
33837		},
33838
33839		keydown: function( event ) {
33840			var preventDefault = true;
33841			switch ( event.keyCode ) {
33842			case $.ui.keyCode.TAB:
33843			case $.ui.keyCode.ESCAPE:
33844				this.close( event );
33845				preventDefault = false;
33846				break;
33847			case $.ui.keyCode.ENTER:
33848				if ( this.isOpen ) {
33849					this._selectFocusedItem( event );
33850				}
33851				break;
33852			case $.ui.keyCode.UP:
33853				if ( event.altKey ) {
33854					this._toggle( event );
33855				} else {
33856					this._move( "prev", event );
33857				}
33858				break;
33859			case $.ui.keyCode.DOWN:
33860				if ( event.altKey ) {
33861					this._toggle( event );
33862				} else {
33863					this._move( "next", event );
33864				}
33865				break;
33866			case $.ui.keyCode.SPACE:
33867				if ( this.isOpen ) {
33868					this._selectFocusedItem( event );
33869				} else {
33870					this._toggle( event );
33871				}
33872				break;
33873			case $.ui.keyCode.LEFT:
33874				this._move( "prev", event );
33875				break;
33876			case $.ui.keyCode.RIGHT:
33877				this._move( "next", event );
33878				break;
33879			case $.ui.keyCode.HOME:
33880			case $.ui.keyCode.PAGE_UP:
33881				this._move( "first", event );
33882				break;
33883			case $.ui.keyCode.END:
33884			case $.ui.keyCode.PAGE_DOWN:
33885				this._move( "last", event );
33886				break;
33887			default:
33888				this.menu.trigger( event );
33889				preventDefault = false;
33890			}
33891
33892			if ( preventDefault ) {
33893				event.preventDefault();
33894			}
33895		}
33896	},
33897
33898	_selectFocusedItem: function( event ) {
33899		var item = this.menuItems.eq( this.focusIndex ).parent( "li" );
33900		if ( !item.hasClass( "ui-state-disabled" ) ) {
33901			this._select( item.data( "ui-selectmenu-item" ), event );
33902		}
33903	},
33904
33905	_select: function( item, event ) {
33906		var oldIndex = this.element[ 0 ].selectedIndex;
33907
33908		// Change native select element
33909		this.element[ 0 ].selectedIndex = item.index;
33910		this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );
33911		this._setAria( item );
33912		this._trigger( "select", event, { item: item } );
33913
33914		if ( item.index !== oldIndex ) {
33915			this._trigger( "change", event, { item: item } );
33916		}
33917
33918		this.close( event );
33919	},
33920
33921	_setAria: function( item ) {
33922		var id = this.menuItems.eq( item.index ).attr( "id" );
33923
33924		this.button.attr( {
33925			"aria-labelledby": id,
33926			"aria-activedescendant": id
33927		} );
33928		this.menu.attr( "aria-activedescendant", id );
33929	},
33930
33931	_setOption: function( key, value ) {
33932		if ( key === "icons" ) {
33933			var icon = this.button.find( "span.ui-icon" );
33934			this._removeClass( icon, null, this.options.icons.button )
33935				._addClass( icon, null, value.button );
33936		}
33937
33938		this._super( key, value );
33939
33940		if ( key === "appendTo" ) {
33941			this.menuWrap.appendTo( this._appendTo() );
33942		}
33943
33944		if ( key === "width" ) {
33945			this._resizeButton();
33946		}
33947	},
33948
33949	_setOptionDisabled: function( value ) {
33950		this._super( value );
33951
33952		this.menuInstance.option( "disabled", value );
33953		this.button.attr( "aria-disabled", value );
33954		this._toggleClass( this.button, null, "ui-state-disabled", value );
33955
33956		this.element.prop( "disabled", value );
33957		if ( value ) {
33958			this.button.attr( "tabindex", -1 );
33959			this.close();
33960		} else {
33961			this.button.attr( "tabindex", 0 );
33962		}
33963	},
33964
33965	_appendTo: function() {
33966		var element = this.options.appendTo;
33967
33968		if ( element ) {
33969			element = element.jquery || element.nodeType ?
33970				$( element ) :
33971				this.document.find( element ).eq( 0 );
33972		}
33973
33974		if ( !element || !element[ 0 ] ) {
33975			element = this.element.closest( ".ui-front, dialog" );
33976		}
33977
33978		if ( !element.length ) {
33979			element = this.document[ 0 ].body;
33980		}
33981
33982		return element;
33983	},
33984
33985	_toggleAttr: function() {
33986		this.button.attr( "aria-expanded", this.isOpen );
33987
33988		// We can't use two _toggleClass() calls here, because we need to make sure
33989		// we always remove classes first and add them second, otherwise if both classes have the
33990		// same theme class, it will be removed after we add it.
33991		this._removeClass( this.button, "ui-selectmenu-button-" +
33992			( this.isOpen ? "closed" : "open" ) )
33993			._addClass( this.button, "ui-selectmenu-button-" +
33994				( this.isOpen ? "open" : "closed" ) )
33995			._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen );
33996
33997		this.menu.attr( "aria-hidden", !this.isOpen );
33998	},
33999
34000	_resizeButton: function() {
34001		var width = this.options.width;
34002
34003		// For `width: false`, just remove inline style and stop
34004		if ( width === false ) {
34005			this.button.css( "width", "" );
34006			return;
34007		}
34008
34009		// For `width: null`, match the width of the original element
34010		if ( width === null ) {
34011			width = this.element.show().outerWidth();
34012			this.element.hide();
34013		}
34014
34015		this.button.outerWidth( width );
34016	},
34017
34018	_resizeMenu: function() {
34019		this.menu.outerWidth( Math.max(
34020			this.button.outerWidth(),
34021
34022			// Support: IE10
34023			// IE10 wraps long text (possibly a rounding bug)
34024			// so we add 1px to avoid the wrapping
34025			this.menu.width( "" ).outerWidth() + 1
34026		) );
34027	},
34028
34029	_getCreateOptions: function() {
34030		var options = this._super();
34031
34032		options.disabled = this.element.prop( "disabled" );
34033
34034		return options;
34035	},
34036
34037	_parseOptions: function( options ) {
34038		var that = this,
34039			data = [];
34040		options.each( function( index, item ) {
34041			data.push( that._parseOption( $( item ), index ) );
34042		} );
34043		this.items = data;
34044	},
34045
34046	_parseOption: function( option, index ) {
34047		var optgroup = option.parent( "optgroup" );
34048
34049		return {
34050			element: option,
34051			index: index,
34052			value: option.val(),
34053			label: option.text(),
34054			optgroup: optgroup.attr( "label" ) || "",
34055			disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" )
34056		};
34057	},
34058
34059	_destroy: function() {
34060		this._unbindFormResetHandler();
34061		this.menuWrap.remove();
34062		this.button.remove();
34063		this.element.show();
34064		this.element.removeUniqueId();
34065		this.labels.attr( "for", this.ids.element );
34066	}
34067} ] );
34068
34069} ) );
34070
34071
34072
34073
34074
34075/*!
34076 * jQuery UI Slider 1.12.1
34077 * http://jqueryui.com
34078 *
34079 * Copyright jQuery Foundation and other contributors
34080 * Released under the MIT license.
34081 * http://jquery.org/license
34082 */
34083
34084//>>label: Slider
34085//>>group: Widgets
34086//>>description: Displays a flexible slider with ranges and accessibility via keyboard.
34087//>>docs: http://api.jqueryui.com/slider/
34088//>>demos: http://jqueryui.com/slider/
34089//>>css.structure: ../../themes/base/core.css
34090//>>css.structure: ../../themes/base/slider.css
34091//>>css.theme: ../../themes/base/theme.css
34092
34093( function( factory ) {
34094	if ( typeof define === "function" && define.amd ) {
34095
34096		// AMD. Register as an anonymous module.
34097		define( [
34098			"jquery",
34099			"./mouse",
34100			"../keycode",
34101			"../version",
34102			"../widget"
34103		], factory );
34104	} else {
34105
34106		// Browser globals
34107		factory( jQuery );
34108	}
34109}( function( $ ) {
34110
34111return
vendor: 18,715 bytes, lines 34111-34825
34111 $.widget( "ui.slider", $.ui.mouse, {
34112	version: "1.12.1",
34113	widgetEventPrefix: "slide",
34114
34115	options: {
34116		animate: false,
34117		classes: {
34118			"ui-slider": "ui-corner-all",
34119			"ui-slider-handle": "ui-corner-all",
34120
34121			// Note: ui-widget-header isn't the most fittingly semantic framework class for this
34122			// element, but worked best visually with a variety of themes
34123			"ui-slider-range": "ui-corner-all ui-widget-header"
34124		},
34125		distance: 0,
34126		max: 100,
34127		min: 0,
34128		orientation: "horizontal",
34129		range: false,
34130		step: 1,
34131		value: 0,
34132		values: null,
34133
34134		// Callbacks
34135		change: null,
34136		slide: null,
34137		start: null,
34138		stop: null
34139	},
34140
34141	// Number of pages in a slider
34142	// (how many times can you page up/down to go through the whole range)
34143	numPages: 5,
34144
34145	_create: function() {
34146		this._keySliding = false;
34147		this._mouseSliding = false;
34148		this._animateOff = true;
34149		this._handleIndex = null;
34150		this._detectOrientation();
34151		this._mouseInit();
34152		this._calculateNewMax();
34153
34154		this._addClass( "ui-slider ui-slider-" + this.orientation,
34155			"ui-widget ui-widget-content" );
34156
34157		this._refresh();
34158
34159		this._animateOff = false;
34160	},
34161
34162	_refresh: function() {
34163		this._createRange();
34164		this._createHandles();
34165		this._setupEvents();
34166		this._refreshValue();
34167	},
34168
34169	_createHandles: function() {
34170		var i, handleCount,
34171			options = this.options,
34172			existingHandles = this.element.find( ".ui-slider-handle" ),
34173			handle = "<span tabindex='0'></span>",
34174			handles = [];
34175
34176		handleCount = ( options.values && options.values.length ) || 1;
34177
34178		if ( existingHandles.length > handleCount ) {
34179			existingHandles.slice( handleCount ).remove();
34180			existingHandles = existingHandles.slice( 0, handleCount );
34181		}
34182
34183		for ( i = existingHandles.length; i < handleCount; i++ ) {
34184			handles.push( handle );
34185		}
34186
34187		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
34188
34189		this._addClass( this.handles, "ui-slider-handle", "ui-state-default" );
34190
34191		this.handle = this.handles.eq( 0 );
34192
34193		this.handles.each( function( i ) {
34194			$( this )
34195				.data( "ui-slider-handle-index", i )
34196				.attr( "tabIndex", 0 );
34197		} );
34198	},
34199
34200	_createRange: function() {
34201		var options = this.options;
34202
34203		if ( options.range ) {
34204			if ( options.range === true ) {
34205				if ( !options.values ) {
34206					options.values = [ this._valueMin(), this._valueMin() ];
34207				} else if ( options.values.length && options.values.length !== 2 ) {
34208					options.values = [ options.values[ 0 ], options.values[ 0 ] ];
34209				} else if ( $.isArray( options.values ) ) {
34210					options.values = options.values.slice( 0 );
34211				}
34212			}
34213
34214			if ( !this.range || !this.range.length ) {
34215				this.range = $( "<div>" )
34216					.appendTo( this.element );
34217
34218				this._addClass( this.range, "ui-slider-range" );
34219			} else {
34220				this._removeClass( this.range, "ui-slider-range-min ui-slider-range-max" );
34221
34222				// Handle range switching from true to min/max
34223				this.range.css( {
34224					"left": "",
34225					"bottom": ""
34226				} );
34227			}
34228			if ( options.range === "min" || options.range === "max" ) {
34229				this._addClass( this.range, "ui-slider-range-" + options.range );
34230			}
34231		} else {
34232			if ( this.range ) {
34233				this.range.remove();
34234			}
34235			this.range = null;
34236		}
34237	},
34238
34239	_setupEvents: function() {
34240		this._off( this.handles );
34241		this._on( this.handles, this._handleEvents );
34242		this._hoverable( this.handles );
34243		this._focusable( this.handles );
34244	},
34245
34246	_destroy: function() {
34247		this.handles.remove();
34248		if ( this.range ) {
34249			this.range.remove();
34250		}
34251
34252		this._mouseDestroy();
34253	},
34254
34255	_mouseCapture: function( event ) {
34256		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
34257			that = this,
34258			o = this.options;
34259
34260		if ( o.disabled ) {
34261			return false;
34262		}
34263
34264		this.elementSize = {
34265			width: this.element.outerWidth(),
34266			height: this.element.outerHeight()
34267		};
34268		this.elementOffset = this.element.offset();
34269
34270		position = { x: event.pageX, y: event.pageY };
34271		normValue = this._normValueFromMouse( position );
34272		distance = this._valueMax() - this._valueMin() + 1;
34273		this.handles.each( function( i ) {
34274			var thisDistance = Math.abs( normValue - that.values( i ) );
34275			if ( ( distance > thisDistance ) ||
34276				( distance === thisDistance &&
34277					( i === that._lastChangedValue || that.values( i ) === o.min ) ) ) {
34278				distance = thisDistance;
34279				closestHandle = $( this );
34280				index = i;
34281			}
34282		} );
34283
34284		allowed = this._start( event, index );
34285		if ( allowed === false ) {
34286			return false;
34287		}
34288		this._mouseSliding = true;
34289
34290		this._handleIndex = index;
34291
34292		this._addClass( closestHandle, null, "ui-state-active" );
34293		closestHandle.trigger( "focus" );
34294
34295		offset = closestHandle.offset();
34296		mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
34297		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
34298			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
34299			top: event.pageY - offset.top -
34300				( closestHandle.height() / 2 ) -
34301				( parseInt( closestHandle.css( "borderTopWidth" ), 10 ) || 0 ) -
34302				( parseInt( closestHandle.css( "borderBottomWidth" ), 10 ) || 0 ) +
34303				( parseInt( closestHandle.css( "marginTop" ), 10 ) || 0 )
34304		};
34305
34306		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
34307			this._slide( event, index, normValue );
34308		}
34309		this._animateOff = true;
34310		return true;
34311	},
34312
34313	_mouseStart: function() {
34314		return true;
34315	},
34316
34317	_mouseDrag: function( event ) {
34318		var position = { x: event.pageX, y: event.pageY },
34319			normValue = this._normValueFromMouse( position );
34320
34321		this._slide( event, this._handleIndex, normValue );
34322
34323		return false;
34324	},
34325
34326	_mouseStop: function( event ) {
34327		this._removeClass( this.handles, null, "ui-state-active" );
34328		this._mouseSliding = false;
34329
34330		this._stop( event, this._handleIndex );
34331		this._change( event, this._handleIndex );
34332
34333		this._handleIndex = null;
34334		this._clickOffset = null;
34335		this._animateOff = false;
34336
34337		return false;
34338	},
34339
34340	_detectOrientation: function() {
34341		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
34342	},
34343
34344	_normValueFromMouse: function( position ) {
34345		var pixelTotal,
34346			pixelMouse,
34347			percentMouse,
34348			valueTotal,
34349			valueMouse;
34350
34351		if ( this.orientation === "horizontal" ) {
34352			pixelTotal = this.elementSize.width;
34353			pixelMouse = position.x - this.elementOffset.left -
34354				( this._clickOffset ? this._clickOffset.left : 0 );
34355		} else {
34356			pixelTotal = this.elementSize.height;
34357			pixelMouse = position.y - this.elementOffset.top -
34358				( this._clickOffset ? this._clickOffset.top : 0 );
34359		}
34360
34361		percentMouse = ( pixelMouse / pixelTotal );
34362		if ( percentMouse > 1 ) {
34363			percentMouse = 1;
34364		}
34365		if ( percentMouse < 0 ) {
34366			percentMouse = 0;
34367		}
34368		if ( this.orientation === "vertical" ) {
34369			percentMouse = 1 - percentMouse;
34370		}
34371
34372		valueTotal = this._valueMax() - this._valueMin();
34373		valueMouse = this._valueMin() + percentMouse * valueTotal;
34374
34375		return this._trimAlignValue( valueMouse );
34376	},
34377
34378	_uiHash: function( index, value, values ) {
34379		var uiHash = {
34380			handle: this.handles[ index ],
34381			handleIndex: index,
34382			value: value !== undefined ? value : this.value()
34383		};
34384
34385		if ( this._hasMultipleValues() ) {
34386			uiHash.value = value !== undefined ? value : this.values( index );
34387			uiHash.values = values || this.values();
34388		}
34389
34390		return uiHash;
34391	},
34392
34393	_hasMultipleValues: function() {
34394		return this.options.values && this.options.values.length;
34395	},
34396
34397	_start: function( event, index ) {
34398		return this._trigger( "start", event, this._uiHash( index ) );
34399	},
34400
34401	_slide: function( event, index, newVal ) {
34402		var allowed, otherVal,
34403			currentValue = this.value(),
34404			newValues = this.values();
34405
34406		if ( this._hasMultipleValues() ) {
34407			otherVal = this.values( index ? 0 : 1 );
34408			currentValue = this.values( index );
34409
34410			if ( this.options.values.length === 2 && this.options.range === true ) {
34411				newVal =  index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal );
34412			}
34413
34414			newValues[ index ] = newVal;
34415		}
34416
34417		if ( newVal === currentValue ) {
34418			return;
34419		}
34420
34421		allowed = this._trigger( "slide", event, this._uiHash( index, newVal, newValues ) );
34422
34423		// A slide can be canceled by returning false from the slide callback
34424		if ( allowed === false ) {
34425			return;
34426		}
34427
34428		if ( this._hasMultipleValues() ) {
34429			this.values( index, newVal );
34430		} else {
34431			this.value( newVal );
34432		}
34433	},
34434
34435	_stop: function( event, index ) {
34436		this._trigger( "stop", event, this._uiHash( index ) );
34437	},
34438
34439	_change: function( event, index ) {
34440		if ( !this._keySliding && !this._mouseSliding ) {
34441
34442			//store the last changed value index for reference when handles overlap
34443			this._lastChangedValue = index;
34444			this._trigger( "change", event, this._uiHash( index ) );
34445		}
34446	},
34447
34448	value: function( newValue ) {
34449		if ( arguments.length ) {
34450			this.options.value = this._trimAlignValue( newValue );
34451			this._refreshValue();
34452			this._change( null, 0 );
34453			return;
34454		}
34455
34456		return this._value();
34457	},
34458
34459	values: function( index, newValue ) {
34460		var vals,
34461			newValues,
34462			i;
34463
34464		if ( arguments.length > 1 ) {
34465			this.options.values[ index ] = this._trimAlignValue( newValue );
34466			this._refreshValue();
34467			this._change( null, index );
34468			return;
34469		}
34470
34471		if ( arguments.length ) {
34472			if ( $.isArray( arguments[ 0 ] ) ) {
34473				vals = this.options.values;
34474				newValues = arguments[ 0 ];
34475				for ( i = 0; i < vals.length; i += 1 ) {
34476					vals[ i ] = this._trimAlignValue( newValues[ i ] );
34477					this._change( null, i );
34478				}
34479				this._refreshValue();
34480			} else {
34481				if ( this._hasMultipleValues() ) {
34482					return this._values( index );
34483				} else {
34484					return this.value();
34485				}
34486			}
34487		} else {
34488			return this._values();
34489		}
34490	},
34491
34492	_setOption: function( key, value ) {
34493		var i,
34494			valsLength = 0;
34495
34496		if ( key === "range" && this.options.range === true ) {
34497			if ( value === "min" ) {
34498				this.options.value = this._values( 0 );
34499				this.options.values = null;
34500			} else if ( value === "max" ) {
34501				this.options.value = this._values( this.options.values.length - 1 );
34502				this.options.values = null;
34503			}
34504		}
34505
34506		if ( $.isArray( this.options.values ) ) {
34507			valsLength = this.options.values.length;
34508		}
34509
34510		this._super( key, value );
34511
34512		switch ( key ) {
34513			case "orientation":
34514				this._detectOrientation();
34515				this._removeClass( "ui-slider-horizontal ui-slider-vertical" )
34516					._addClass( "ui-slider-" + this.orientation );
34517				this._refreshValue();
34518				if ( this.options.range ) {
34519					this._refreshRange( value );
34520				}
34521
34522				// Reset positioning from previous orientation
34523				this.handles.css( value === "horizontal" ? "bottom" : "left", "" );
34524				break;
34525			case "value":
34526				this._animateOff = true;
34527				this._refreshValue();
34528				this._change( null, 0 );
34529				this._animateOff = false;
34530				break;
34531			case "values":
34532				this._animateOff = true;
34533				this._refreshValue();
34534
34535				// Start from the last handle to prevent unreachable handles (#9046)
34536				for ( i = valsLength - 1; i >= 0; i-- ) {
34537					this._change( null, i );
34538				}
34539				this._animateOff = false;
34540				break;
34541			case "step":
34542			case "min":
34543			case "max":
34544				this._animateOff = true;
34545				this._calculateNewMax();
34546				this._refreshValue();
34547				this._animateOff = false;
34548				break;
34549			case "range":
34550				this._animateOff = true;
34551				this._refresh();
34552				this._animateOff = false;
34553				break;
34554		}
34555	},
34556
34557	_setOptionDisabled: function( value ) {
34558		this._super( value );
34559
34560		this._toggleClass( null, "ui-state-disabled", !!value );
34561	},
34562
34563	//internal value getter
34564	// _value() returns value trimmed by min and max, aligned by step
34565	_value: function() {
34566		var val = this.options.value;
34567		val = this._trimAlignValue( val );
34568
34569		return val;
34570	},
34571
34572	//internal values getter
34573	// _values() returns array of values trimmed by min and max, aligned by step
34574	// _values( index ) returns single value trimmed by min and max, aligned by step
34575	_values: function( index ) {
34576		var val,
34577			vals,
34578			i;
34579
34580		if ( arguments.length ) {
34581			val = this.options.values[ index ];
34582			val = this._trimAlignValue( val );
34583
34584			return val;
34585		} else if ( this._hasMultipleValues() ) {
34586
34587			// .slice() creates a copy of the array
34588			// this copy gets trimmed by min and max and then returned
34589			vals = this.options.values.slice();
34590			for ( i = 0; i < vals.length; i += 1 ) {
34591				vals[ i ] = this._trimAlignValue( vals[ i ] );
34592			}
34593
34594			return vals;
34595		} else {
34596			return [];
34597		}
34598	},
34599
34600	// Returns the step-aligned value that val is closest to, between (inclusive) min and max
34601	_trimAlignValue: function( val ) {
34602		if ( val <= this._valueMin() ) {
34603			return this._valueMin();
34604		}
34605		if ( val >= this._valueMax() ) {
34606			return this._valueMax();
34607		}
34608		var step = ( this.options.step > 0 ) ? this.options.step : 1,
34609			valModStep = ( val - this._valueMin() ) % step,
34610			alignValue = val - valModStep;
34611
34612		if ( Math.abs( valModStep ) * 2 >= step ) {
34613			alignValue += ( valModStep > 0 ) ? step : ( -step );
34614		}
34615
34616		// Since JavaScript has problems with large floats, round
34617		// the final value to 5 digits after the decimal point (see #4124)
34618		return parseFloat( alignValue.toFixed( 5 ) );
34619	},
34620
34621	_calculateNewMax: function() {
34622		var max = this.options.max,
34623			min = this._valueMin(),
34624			step = this.options.step,
34625			aboveMin = Math.round( ( max - min ) / step ) * step;
34626		max = aboveMin + min;
34627		if ( max > this.options.max ) {
34628
34629			//If max is not divisible by step, rounding off may increase its value
34630			max -= step;
34631		}
34632		this.max = parseFloat( max.toFixed( this._precision() ) );
34633	},
34634
34635	_precision: function() {
34636		var precision = this._precisionOf( this.options.step );
34637		if ( this.options.min !== null ) {
34638			precision = Math.max( precision, this._precisionOf( this.options.min ) );
34639		}
34640		return precision;
34641	},
34642
34643	_precisionOf: function( num ) {
34644		var str = num.toString(),
34645			decimal = str.indexOf( "." );
34646		return decimal === -1 ? 0 : str.length - decimal - 1;
34647	},
34648
34649	_valueMin: function() {
34650		return this.options.min;
34651	},
34652
34653	_valueMax: function() {
34654		return this.max;
34655	},
34656
34657	_refreshRange: function( orientation ) {
34658		if ( orientation === "vertical" ) {
34659			this.range.css( { "width": "", "left": "" } );
34660		}
34661		if ( orientation === "horizontal" ) {
34662			this.range.css( { "height": "", "bottom": "" } );
34663		}
34664	},
34665
34666	_refreshValue: function() {
34667		var lastValPercent, valPercent, value, valueMin, valueMax,
34668			oRange = this.options.range,
34669			o = this.options,
34670			that = this,
34671			animate = ( !this._animateOff ) ? o.animate : false,
34672			_set = {};
34673
34674		if ( this._hasMultipleValues() ) {
34675			this.handles.each( function( i ) {
34676				valPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() -
34677					that._valueMin() ) * 100;
34678				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
34679				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
34680				if ( that.options.range === true ) {
34681					if ( that.orientation === "horizontal" ) {
34682						if ( i === 0 ) {
34683							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
34684								left: valPercent + "%"
34685							}, o.animate );
34686						}
34687						if ( i === 1 ) {
34688							that.range[ animate ? "animate" : "css" ]( {
34689								width: ( valPercent - lastValPercent ) + "%"
34690							}, {
34691								queue: false,
34692								duration: o.animate
34693							} );
34694						}
34695					} else {
34696						if ( i === 0 ) {
34697							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
34698								bottom: ( valPercent ) + "%"
34699							}, o.animate );
34700						}
34701						if ( i === 1 ) {
34702							that.range[ animate ? "animate" : "css" ]( {
34703								height: ( valPercent - lastValPercent ) + "%"
34704							}, {
34705								queue: false,
34706								duration: o.animate
34707							} );
34708						}
34709					}
34710				}
34711				lastValPercent = valPercent;
34712			} );
34713		} else {
34714			value = this.value();
34715			valueMin = this._valueMin();
34716			valueMax = this._valueMax();
34717			valPercent = ( valueMax !== valueMin ) ?
34718					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
34719					0;
34720			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
34721			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
34722
34723			if ( oRange === "min" && this.orientation === "horizontal" ) {
34724				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
34725					width: valPercent + "%"
34726				}, o.animate );
34727			}
34728			if ( oRange === "max" && this.orientation === "horizontal" ) {
34729				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
34730					width: ( 100 - valPercent ) + "%"
34731				}, o.animate );
34732			}
34733			if ( oRange === "min" && this.orientation === "vertical" ) {
34734				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
34735					height: valPercent + "%"
34736				}, o.animate );
34737			}
34738			if ( oRange === "max" && this.orientation === "vertical" ) {
34739				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
34740					height: ( 100 - valPercent ) + "%"
34741				}, o.animate );
34742			}
34743		}
34744	},
34745
34746	_handleEvents: {
34747		keydown: function( event ) {
34748			var allowed, curVal, newVal, step,
34749				index = $( event.target ).data( "ui-slider-handle-index" );
34750
34751			switch ( event.keyCode ) {
34752				case $.ui.keyCode.HOME:
34753				case $.ui.keyCode.END:
34754				case $.ui.keyCode.PAGE_UP:
34755				case $.ui.keyCode.PAGE_DOWN:
34756				case $.ui.keyCode.UP:
34757				case $.ui.keyCode.RIGHT:
34758				case $.ui.keyCode.DOWN:
34759				case $.ui.keyCode.LEFT:
34760					event.preventDefault();
34761					if ( !this._keySliding ) {
34762						this._keySliding = true;
34763						this._addClass( $( event.target ), null, "ui-state-active" );
34764						allowed = this._start( event, index );
34765						if ( allowed === false ) {
34766							return;
34767						}
34768					}
34769					break;
34770			}
34771
34772			step = this.options.step;
34773			if ( this._hasMultipleValues() ) {
34774				curVal = newVal = this.values( index );
34775			} else {
34776				curVal = newVal = this.value();
34777			}
34778
34779			switch ( event.keyCode ) {
34780				case $.ui.keyCode.HOME:
34781					newVal = this._valueMin();
34782					break;
34783				case $.ui.keyCode.END:
34784					newVal = this._valueMax();
34785					break;
34786				case $.ui.keyCode.PAGE_UP:
34787					newVal = this._trimAlignValue(
34788						curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages )
34789					);
34790					break;
34791				case $.ui.keyCode.PAGE_DOWN:
34792					newVal = this._trimAlignValue(
34793						curVal - ( ( this._valueMax() - this._valueMin() ) / this.numPages ) );
34794					break;
34795				case $.ui.keyCode.UP:
34796				case $.ui.keyCode.RIGHT:
34797					if ( curVal === this._valueMax() ) {
34798						return;
34799					}
34800					newVal = this._trimAlignValue( curVal + step );
34801					break;
34802				case $.ui.keyCode.DOWN:
34803				case $.ui.keyCode.LEFT:
34804					if ( curVal === this._valueMin() ) {
34805						return;
34806					}
34807					newVal = this._trimAlignValue( curVal - step );
34808					break;
34809			}
34810
34811			this._slide( event, index, newVal );
34812		},
34813		keyup: function( event ) {
34814			var index = $( event.target ).data( "ui-slider-handle-index" );
34815
34816			if ( this._keySliding ) {
34817				this._keySliding = false;
34818				this._stop( event, index );
34819				this._change( event, index );
34820				this._removeClass( $( event.target ), null, "ui-state-active" );
34821			}
34822		}
34823	}
34824} );
34825
34826} ) );
34827
34828
34829
34830
34831
34832
34833
34834/*!
34835 * jQuery UI Sortable 1.12.1
34836 * http://jqueryui.com
34837 *
34838 * Copyright jQuery Foundation and other contributors
34839 * Released under the MIT license.
34840 * http://jquery.org/license
34841 */
34842
34843//>>label: Sortable
34844//>>group: Interactions
34845//>>description: Enables items in a list to be sorted using the mouse.
34846//>>docs: http://api.jqueryui.com/sortable/
34847//>>demos: http://jqueryui.com/sortable/
34848//>>css.structure: ../../themes/base/sortable.css
34849
34850( function( factory ) {
34851	if ( typeof define === "function" && define.amd ) {
34852
34853		// AMD. Register as an anonymous module.
34854		define( [
34855			"jquery",
34856			"./mouse",
34857			"../data",
34858			"../ie",
34859			"../scroll-parent",
34860			"../version",
34861			"../widget"
34862		], factory );
34863	} else {
34864
34865		// Browser globals
34866		factory( jQuery );
34867	}
34868}( function( $ ) {
34869
34870return
vendor: 45,323 bytes, lines 34870-36386
34870 $.widget( "ui.sortable", $.ui.mouse, {
34871	version: "1.12.1",
34872	widgetEventPrefix: "sort",
34873	ready: false,
34874	options: {
34875		appendTo: "parent",
34876		axis: false,
34877		connectWith: false,
34878		containment: false,
34879		cursor: "auto",
34880		cursorAt: false,
34881		dropOnEmpty: true,
34882		forcePlaceholderSize: false,
34883		forceHelperSize: false,
34884		grid: false,
34885		handle: false,
34886		helper: "original",
34887		items: "> *",
34888		opacity: false,
34889		placeholder: false,
34890		revert: false,
34891		scroll: true,
34892		scrollSensitivity: 20,
34893		scrollSpeed: 20,
34894		scope: "default",
34895		tolerance: "intersect",
34896		zIndex: 1000,
34897
34898		// Callbacks
34899		activate: null,
34900		beforeStop: null,
34901		change: null,
34902		deactivate: null,
34903		out: null,
34904		over: null,
34905		receive: null,
34906		remove: null,
34907		sort: null,
34908		start: null,
34909		stop: null,
34910		update: null
34911	},
34912
34913	_isOverAxis: function( x, reference, size ) {
34914		return ( x >= reference ) && ( x < ( reference + size ) );
34915	},
34916
34917	_isFloating: function( item ) {
34918		return ( /left|right/ ).test( item.css( "float" ) ) ||
34919			( /inline|table-cell/ ).test( item.css( "display" ) );
34920	},
34921
34922	_create: function() {
34923		this.containerCache = {};
34924		this._addClass( "ui-sortable" );
34925
34926		//Get the items
34927		this.refresh();
34928
34929		//Let's determine the parent's offset
34930		this.offset = this.element.offset();
34931
34932		//Initialize mouse events for interaction
34933		this._mouseInit();
34934
34935		this._setHandleClassName();
34936
34937		//We're ready to go
34938		this.ready = true;
34939
34940	},
34941
34942	_setOption: function( key, value ) {
34943		this._super( key, value );
34944
34945		if ( key === "handle" ) {
34946			this._setHandleClassName();
34947		}
34948	},
34949
34950	_setHandleClassName: function() {
34951		var that = this;
34952		this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" );
34953		$.each( this.items, function() {
34954			that._addClass(
34955				this.instance.options.handle ?
34956					this.item.find( this.instance.options.handle ) :
34957					this.item,
34958				"ui-sortable-handle"
34959			);
34960		} );
34961	},
34962
34963	_destroy: function() {
34964		this._mouseDestroy();
34965
34966		for ( var i = this.items.length - 1; i >= 0; i-- ) {
34967			this.items[ i ].item.removeData( this.widgetName + "-item" );
34968		}
34969
34970		return this;
34971	},
34972
34973	_mouseCapture: function( event, overrideHandle ) {
34974		var currentItem = null,
34975			validHandle = false,
34976			that = this;
34977
34978		if ( this.reverting ) {
34979			return false;
34980		}
34981
34982		if ( this.options.disabled || this.options.type === "static" ) {
34983			return false;
34984		}
34985
34986		//We have to refresh the items data once first
34987		this._refreshItems( event );
34988
34989		//Find out if the clicked node (or one of its parents) is a actual item in this.items
34990		$( event.target ).parents().each( function() {
34991			if ( $.data( this, that.widgetName + "-item" ) === that ) {
34992				currentItem = $( this );
34993				return false;
34994			}
34995		} );
34996		if ( $.data( event.target, that.widgetName + "-item" ) === that ) {
34997			currentItem = $( event.target );
34998		}
34999
35000		if ( !currentItem ) {
35001			return false;
35002		}
35003		if ( this.options.handle && !overrideHandle ) {
35004			$( this.options.handle, currentItem ).find( "*" ).addBack().each( function() {
35005				if ( this === event.target ) {
35006					validHandle = true;
35007				}
35008			} );
35009			if ( !validHandle ) {
35010				return false;
35011			}
35012		}
35013
35014		this.currentItem = currentItem;
35015		this._removeCurrentsFromItems();
35016		return true;
35017
35018	},
35019
35020	_mouseStart: function( event, overrideHandle, noActivation ) {
35021
35022		var i, body,
35023			o = this.options;
35024
35025		this.currentContainer = this;
35026
35027		//We only need to call refreshPositions, because the refreshItems call has been moved to
35028		// mouseCapture
35029		this.refreshPositions();
35030
35031		//Create and append the visible helper
35032		this.helper = this._createHelper( event );
35033
35034		//Cache the helper size
35035		this._cacheHelperProportions();
35036
35037		/*
35038		 * - Position generation -
35039		 * This block generates everything position related - it's the core of draggables.
35040		 */
35041
35042		//Cache the margins of the original element
35043		this._cacheMargins();
35044
35045		//Get the next scrolling parent
35046		this.scrollParent = this.helper.scrollParent();
35047
35048		//The element's absolute position on the page minus margins
35049		this.offset = this.currentItem.offset();
35050		this.offset = {
35051			top: this.offset.top - this.margins.top,
35052			left: this.offset.left - this.margins.left
35053		};
35054
35055		$.extend( this.offset, {
35056			click: { //Where the click happened, relative to the element
35057				left: event.pageX - this.offset.left,
35058				top: event.pageY - this.offset.top
35059			},
35060			parent: this._getParentOffset(),
35061
35062			// This is a relative to absolute position minus the actual position calculation -
35063			// only used for relative positioned helper
35064			relative: this._getRelativeOffset()
35065		} );
35066
35067		// Only after we got the offset, we can change the helper's position to absolute
35068		// TODO: Still need to figure out a way to make relative sorting possible
35069		this.helper.css( "position", "absolute" );
35070		this.cssPosition = this.helper.css( "position" );
35071
35072		//Generate the original position
35073		this.originalPosition = this._generatePosition( event );
35074		this.originalPageX = event.pageX;
35075		this.originalPageY = event.pageY;
35076
35077		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
35078		( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
35079
35080		//Cache the former DOM position
35081		this.domPosition = {
35082			prev: this.currentItem.prev()[ 0 ],
35083			parent: this.currentItem.parent()[ 0 ]
35084		};
35085
35086		// If the helper is not the original, hide the original so it's not playing any role during
35087		// the drag, won't cause anything bad this way
35088		if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
35089			this.currentItem.hide();
35090		}
35091
35092		//Create the placeholder
35093		this._createPlaceholder();
35094
35095		//Set a containment if given in the options
35096		if ( o.containment ) {
35097			this._setContainment();
35098		}
35099
35100		if ( o.cursor && o.cursor !== "auto" ) { // cursor option
35101			body = this.document.find( "body" );
35102
35103			// Support: IE
35104			this.storedCursor = body.css( "cursor" );
35105			body.css( "cursor", o.cursor );
35106
35107			this.storedStylesheet =
35108				$( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body );
35109		}
35110
35111		if ( o.opacity ) { // opacity option
35112			if ( this.helper.css( "opacity" ) ) {
35113				this._storedOpacity = this.helper.css( "opacity" );
35114			}
35115			this.helper.css( "opacity", o.opacity );
35116		}
35117
35118		if ( o.zIndex ) { // zIndex option
35119			if ( this.helper.css( "zIndex" ) ) {
35120				this._storedZIndex = this.helper.css( "zIndex" );
35121			}
35122			this.helper.css( "zIndex", o.zIndex );
35123		}
35124
35125		//Prepare scrolling
35126		if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
35127				this.scrollParent[ 0 ].tagName !== "HTML" ) {
35128			this.overflowOffset = this.scrollParent.offset();
35129		}
35130
35131		//Call callbacks
35132		this._trigger( "start", event, this._uiHash() );
35133
35134		//Recache the helper size
35135		if ( !this._preserveHelperProportions ) {
35136			this._cacheHelperProportions();
35137		}
35138
35139		//Post "activate" events to possible containers
35140		if ( !noActivation ) {
35141			for ( i = this.containers.length - 1; i >= 0; i-- ) {
35142				this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
35143			}
35144		}
35145
35146		//Prepare possible droppables
35147		if ( $.ui.ddmanager ) {
35148			$.ui.ddmanager.current = this;
35149		}
35150
35151		if ( $.ui.ddmanager && !o.dropBehaviour ) {
35152			$.ui.ddmanager.prepareOffsets( this, event );
35153		}
35154
35155		this.dragging = true;
35156
35157		this._addClass( this.helper, "ui-sortable-helper" );
35158
35159		// Execute the drag once - this causes the helper not to be visiblebefore getting its
35160		// correct position
35161		this._mouseDrag( event );
35162		return true;
35163
35164	},
35165
35166	_mouseDrag: function( event ) {
35167		var i, item, itemElement, intersection,
35168			o = this.options,
35169			scrolled = false;
35170
35171		//Compute the helpers position
35172		this.position = this._generatePosition( event );
35173		this.positionAbs = this._convertPositionTo( "absolute" );
35174
35175		if ( !this.lastPositionAbs ) {
35176			this.lastPositionAbs = this.positionAbs;
35177		}
35178
35179		//Do scrolling
35180		if ( this.options.scroll ) {
35181			if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
35182					this.scrollParent[ 0 ].tagName !== "HTML" ) {
35183
35184				if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) -
35185						event.pageY < o.scrollSensitivity ) {
35186					this.scrollParent[ 0 ].scrollTop =
35187						scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed;
35188				} else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) {
35189					this.scrollParent[ 0 ].scrollTop =
35190						scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed;
35191				}
35192
35193				if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) -
35194						event.pageX < o.scrollSensitivity ) {
35195					this.scrollParent[ 0 ].scrollLeft = scrolled =
35196						this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed;
35197				} else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) {
35198					this.scrollParent[ 0 ].scrollLeft = scrolled =
35199						this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed;
35200				}
35201
35202			} else {
35203
35204				if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) {
35205					scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed );
35206				} else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) <
35207						o.scrollSensitivity ) {
35208					scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed );
35209				}
35210
35211				if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) {
35212					scrolled = this.document.scrollLeft(
35213						this.document.scrollLeft() - o.scrollSpeed
35214					);
35215				} else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) <
35216						o.scrollSensitivity ) {
35217					scrolled = this.document.scrollLeft(
35218						this.document.scrollLeft() + o.scrollSpeed
35219					);
35220				}
35221
35222			}
35223
35224			if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
35225				$.ui.ddmanager.prepareOffsets( this, event );
35226			}
35227		}
35228
35229		//Regenerate the absolute position used for position checks
35230		this.positionAbs = this._convertPositionTo( "absolute" );
35231
35232		//Set the helper position
35233		if ( !this.options.axis || this.options.axis !== "y" ) {
35234			this.helper[ 0 ].style.left = this.position.left + "px";
35235		}
35236		if ( !this.options.axis || this.options.axis !== "x" ) {
35237			this.helper[ 0 ].style.top = this.position.top + "px";
35238		}
35239
35240		//Rearrange
35241		for ( i = this.items.length - 1; i >= 0; i-- ) {
35242
35243			//Cache variables and intersection, continue if no intersection
35244			item = this.items[ i ];
35245			itemElement = item.item[ 0 ];
35246			intersection = this._intersectsWithPointer( item );
35247			if ( !intersection ) {
35248				continue;
35249			}
35250
35251			// Only put the placeholder inside the current Container, skip all
35252			// items from other containers. This works because when moving
35253			// an item from one container to another the
35254			// currentContainer is switched before the placeholder is moved.
35255			//
35256			// Without this, moving items in "sub-sortables" can cause
35257			// the placeholder to jitter between the outer and inner container.
35258			if ( item.instance !== this.currentContainer ) {
35259				continue;
35260			}
35261
35262			// Cannot intersect with itself
35263			// no useless actions that have been done before
35264			// no action if the item moved is the parent of the item checked
35265			if ( itemElement !== this.currentItem[ 0 ] &&
35266				this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement &&
35267				!$.contains( this.placeholder[ 0 ], itemElement ) &&
35268				( this.options.type === "semi-dynamic" ?
35269					!$.contains( this.element[ 0 ], itemElement ) :
35270					true
35271				)
35272			) {
35273
35274				this.direction = intersection === 1 ? "down" : "up";
35275
35276				if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) {
35277					this._rearrange( event, item );
35278				} else {
35279					break;
35280				}
35281
35282				this._trigger( "change", event, this._uiHash() );
35283				break;
35284			}
35285		}
35286
35287		//Post events to containers
35288		this._contactContainers( event );
35289
35290		//Interconnect with droppables
35291		if ( $.ui.ddmanager ) {
35292			$.ui.ddmanager.drag( this, event );
35293		}
35294
35295		//Call callbacks
35296		this._trigger( "sort", event, this._uiHash() );
35297
35298		this.lastPositionAbs = this.positionAbs;
35299		return false;
35300
35301	},
35302
35303	_mouseStop: function( event, noPropagation ) {
35304
35305		if ( !event ) {
35306			return;
35307		}
35308
35309		//If we are using droppables, inform the manager about the drop
35310		if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
35311			$.ui.ddmanager.drop( this, event );
35312		}
35313
35314		if ( this.options.revert ) {
35315			var that = this,
35316				cur = this.placeholder.offset(),
35317				axis = this.options.axis,
35318				animation = {};
35319
35320			if ( !axis || axis === "x" ) {
35321				animation.left = cur.left - this.offset.parent.left - this.margins.left +
35322					( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
35323						0 :
35324						this.offsetParent[ 0 ].scrollLeft
35325					);
35326			}
35327			if ( !axis || axis === "y" ) {
35328				animation.top = cur.top - this.offset.parent.top - this.margins.top +
35329					( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
35330						0 :
35331						this.offsetParent[ 0 ].scrollTop
35332					);
35333			}
35334			this.reverting = true;
35335			$( this.helper ).animate(
35336				animation,
35337				parseInt( this.options.revert, 10 ) || 500,
35338				function() {
35339					that._clear( event );
35340				}
35341			);
35342		} else {
35343			this._clear( event, noPropagation );
35344		}
35345
35346		return false;
35347
35348	},
35349
35350	cancel: function() {
35351
35352		if ( this.dragging ) {
35353
35354			this._mouseUp( new $.Event( "mouseup", { target: null } ) );
35355
35356			if ( this.options.helper === "original" ) {
35357				this.currentItem.css( this._storedCSS );
35358				this._removeClass( this.currentItem, "ui-sortable-helper" );
35359			} else {
35360				this.currentItem.show();
35361			}
35362
35363			//Post deactivating events to containers
35364			for ( var i = this.containers.length - 1; i >= 0; i-- ) {
35365				this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) );
35366				if ( this.containers[ i ].containerCache.over ) {
35367					this.containers[ i ]._trigger( "out", null, this._uiHash( this ) );
35368					this.containers[ i ].containerCache.over = 0;
35369				}
35370			}
35371
35372		}
35373
35374		if ( this.placeholder ) {
35375
35376			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
35377			// it unbinds ALL events from the original node!
35378			if ( this.placeholder[ 0 ].parentNode ) {
35379				this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
35380			}
35381			if ( this.options.helper !== "original" && this.helper &&
35382					this.helper[ 0 ].parentNode ) {
35383				this.helper.remove();
35384			}
35385
35386			$.extend( this, {
35387				helper: null,
35388				dragging: false,
35389				reverting: false,
35390				_noFinalSort: null
35391			} );
35392
35393			if ( this.domPosition.prev ) {
35394				$( this.domPosition.prev ).after( this.currentItem );
35395			} else {
35396				$( this.domPosition.parent ).prepend( this.currentItem );
35397			}
35398		}
35399
35400		return this;
35401
35402	},
35403
35404	serialize: function( o ) {
35405
35406		var items = this._getItemsAsjQuery( o && o.connected ),
35407			str = [];
35408		o = o || {};
35409
35410		$( items ).each( function() {
35411			var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" )
35412				.match( o.expression || ( /(.+)[\-=_](.+)/ ) );
35413			if ( res ) {
35414				str.push(
35415					( o.key || res[ 1 ] + "[]" ) +
35416					"=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) );
35417			}
35418		} );
35419
35420		if ( !str.length && o.key ) {
35421			str.push( o.key + "=" );
35422		}
35423
35424		return str.join( "&" );
35425
35426	},
35427
35428	toArray: function( o ) {
35429
35430		var items = this._getItemsAsjQuery( o && o.connected ),
35431			ret = [];
35432
35433		o = o || {};
35434
35435		items.each( function() {
35436			ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" );
35437		} );
35438		return ret;
35439
35440	},
35441
35442	/* Be careful with the following core functions */
35443	_intersectsWith: function( item ) {
35444
35445		var x1 = this.positionAbs.left,
35446			x2 = x1 + this.helperProportions.width,
35447			y1 = this.positionAbs.top,
35448			y2 = y1 + this.helperProportions.height,
35449			l = item.left,
35450			r = l + item.width,
35451			t = item.top,
35452			b = t + item.height,
35453			dyClick = this.offset.click.top,
35454			dxClick = this.offset.click.left,
35455			isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t &&
35456				( y1 + dyClick ) < b ),
35457			isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l &&
35458				( x1 + dxClick ) < r ),
35459			isOverElement = isOverElementHeight && isOverElementWidth;
35460
35461		if ( this.options.tolerance === "pointer" ||
35462			this.options.forcePointerForContainers ||
35463			( this.options.tolerance !== "pointer" &&
35464				this.helperProportions[ this.floating ? "width" : "height" ] >
35465				item[ this.floating ? "width" : "height" ] )
35466		) {
35467			return isOverElement;
35468		} else {
35469
35470			return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half
35471				x2 - ( this.helperProportions.width / 2 ) < r && // Left Half
35472				t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half
35473				y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half
35474
35475		}
35476	},
35477
35478	_intersectsWithPointer: function( item ) {
35479		var verticalDirection, horizontalDirection,
35480			isOverElementHeight = ( this.options.axis === "x" ) ||
35481				this._isOverAxis(
35482					this.positionAbs.top + this.offset.click.top, item.top, item.height ),
35483			isOverElementWidth = ( this.options.axis === "y" ) ||
35484				this._isOverAxis(
35485					this.positionAbs.left + this.offset.click.left, item.left, item.width ),
35486			isOverElement = isOverElementHeight && isOverElementWidth;
35487
35488		if ( !isOverElement ) {
35489			return false;
35490		}
35491
35492		verticalDirection = this._getDragVerticalDirection();
35493		horizontalDirection = this._getDragHorizontalDirection();
35494
35495		return this.floating ?
35496			( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 )
35497			: ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) );
35498
35499	},
35500
35501	_intersectsWithSides: function( item ) {
35502
35503		var isOverBottomHalf = this._isOverAxis( this.positionAbs.top +
35504				this.offset.click.top, item.top + ( item.height / 2 ), item.height ),
35505			isOverRightHalf = this._isOverAxis( this.positionAbs.left +
35506				this.offset.click.left, item.left + ( item.width / 2 ), item.width ),
35507			verticalDirection = this._getDragVerticalDirection(),
35508			horizontalDirection = this._getDragHorizontalDirection();
35509
35510		if ( this.floating && horizontalDirection ) {
35511			return ( ( horizontalDirection === "right" && isOverRightHalf ) ||
35512				( horizontalDirection === "left" && !isOverRightHalf ) );
35513		} else {
35514			return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) ||
35515				( verticalDirection === "up" && !isOverBottomHalf ) );
35516		}
35517
35518	},
35519
35520	_getDragVerticalDirection: function() {
35521		var delta = this.positionAbs.top - this.lastPositionAbs.top;
35522		return delta !== 0 && ( delta > 0 ? "down" : "up" );
35523	},
35524
35525	_getDragHorizontalDirection: function() {
35526		var delta = this.positionAbs.left - this.lastPositionAbs.left;
35527		return delta !== 0 && ( delta > 0 ? "right" : "left" );
35528	},
35529
35530	refresh: function( event ) {
35531		this._refreshItems( event );
35532		this._setHandleClassName();
35533		this.refreshPositions();
35534		return this;
35535	},
35536
35537	_connectWith: function() {
35538		var options = this.options;
35539		return options.connectWith.constructor === String ?
35540			[ options.connectWith ] :
35541			options.connectWith;
35542	},
35543
35544	_getItemsAsjQuery: function( connected ) {
35545
35546		var i, j, cur, inst,
35547			items = [],
35548			queries = [],
35549			connectWith = this._connectWith();
35550
35551		if ( connectWith && connected ) {
35552			for ( i = connectWith.length - 1; i >= 0; i-- ) {
35553				cur = $( connectWith[ i ], this.document[ 0 ] );
35554				for ( j = cur.length - 1; j >= 0; j-- ) {
35555					inst = $.data( cur[ j ], this.widgetFullName );
35556					if ( inst && inst !== this && !inst.options.disabled ) {
35557						queries.push( [ $.isFunction( inst.options.items ) ?
35558							inst.options.items.call( inst.element ) :
35559							$( inst.options.items, inst.element )
35560								.not( ".ui-sortable-helper" )
35561								.not( ".ui-sortable-placeholder" ), inst ] );
35562					}
35563				}
35564			}
35565		}
35566
35567		queries.push( [ $.isFunction( this.options.items ) ?
35568			this.options.items
35569				.call( this.element, null, { options: this.options, item: this.currentItem } ) :
35570			$( this.options.items, this.element )
35571				.not( ".ui-sortable-helper" )
35572				.not( ".ui-sortable-placeholder" ), this ] );
35573
35574		function addItems() {
35575			items.push( this );
35576		}
35577		for ( i = queries.length - 1; i >= 0; i-- ) {
35578			queries[ i ][ 0 ].each( addItems );
35579		}
35580
35581		return $( items );
35582
35583	},
35584
35585	_removeCurrentsFromItems: function() {
35586
35587		var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" );
35588
35589		this.items = $.grep( this.items, function( item ) {
35590			for ( var j = 0; j < list.length; j++ ) {
35591				if ( list[ j ] === item.item[ 0 ] ) {
35592					return false;
35593				}
35594			}
35595			return true;
35596		} );
35597
35598	},
35599
35600	_refreshItems: function( event ) {
35601
35602		this.items = [];
35603		this.containers = [ this ];
35604
35605		var i, j, cur, inst, targetData, _queries, item, queriesLength,
35606			items = this.items,
35607			queries = [ [ $.isFunction( this.options.items ) ?
35608				this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) :
35609				$( this.options.items, this.element ), this ] ],
35610			connectWith = this._connectWith();
35611
35612		//Shouldn't be run the first time through due to massive slow-down
35613		if ( connectWith && this.ready ) {
35614			for ( i = connectWith.length - 1; i >= 0; i-- ) {
35615				cur = $( connectWith[ i ], this.document[ 0 ] );
35616				for ( j = cur.length - 1; j >= 0; j-- ) {
35617					inst = $.data( cur[ j ], this.widgetFullName );
35618					if ( inst && inst !== this && !inst.options.disabled ) {
35619						queries.push( [ $.isFunction( inst.options.items ) ?
35620							inst.options.items
35621								.call( inst.element[ 0 ], event, { item: this.currentItem } ) :
35622							$( inst.options.items, inst.element ), inst ] );
35623						this.containers.push( inst );
35624					}
35625				}
35626			}
35627		}
35628
35629		for ( i = queries.length - 1; i >= 0; i-- ) {
35630			targetData = queries[ i ][ 1 ];
35631			_queries = queries[ i ][ 0 ];
35632
35633			for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) {
35634				item = $( _queries[ j ] );
35635
35636				// Data for target checking (mouse manager)
35637				item.data( this.widgetName + "-item", targetData );
35638
35639				items.push( {
35640					item: item,
35641					instance: targetData,
35642					width: 0, height: 0,
35643					left: 0, top: 0
35644				} );
35645			}
35646		}
35647
35648	},
35649
35650	refreshPositions: function( fast ) {
35651
35652		// Determine whether items are being displayed horizontally
35653		this.floating = this.items.length ?
35654			this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) :
35655			false;
35656
35657		//This has to be redone because due to the item being moved out/into the offsetParent,
35658		// the offsetParent's position will change
35659		if ( this.offsetParent && this.helper ) {
35660			this.offset.parent = this._getParentOffset();
35661		}
35662
35663		var i, item, t, p;
35664
35665		for ( i = this.items.length - 1; i >= 0; i-- ) {
35666			item = this.items[ i ];
35667
35668			//We ignore calculating positions of all connected containers when we're not over them
35669			if ( item.instance !== this.currentContainer && this.currentContainer &&
35670					item.item[ 0 ] !== this.currentItem[ 0 ] ) {
35671				continue;
35672			}
35673
35674			t = this.options.toleranceElement ?
35675				$( this.options.toleranceElement, item.item ) :
35676				item.item;
35677
35678			if ( !fast ) {
35679				item.width = t.outerWidth();
35680				item.height = t.outerHeight();
35681			}
35682
35683			p = t.offset();
35684			item.left = p.left;
35685			item.top = p.top;
35686		}
35687
35688		if ( this.options.custom && this.options.custom.refreshContainers ) {
35689			this.options.custom.refreshContainers.call( this );
35690		} else {
35691			for ( i = this.containers.length - 1; i >= 0; i-- ) {
35692				p = this.containers[ i ].element.offset();
35693				this.containers[ i ].containerCache.left = p.left;
35694				this.containers[ i ].containerCache.top = p.top;
35695				this.containers[ i ].containerCache.width =
35696					this.containers[ i ].element.outerWidth();
35697				this.containers[ i ].containerCache.height =
35698					this.containers[ i ].element.outerHeight();
35699			}
35700		}
35701
35702		return this;
35703	},
35704
35705	_createPlaceholder: function( that ) {
35706		that = that || this;
35707		var className,
35708			o = that.options;
35709
35710		if ( !o.placeholder || o.placeholder.constructor === String ) {
35711			className = o.placeholder;
35712			o.placeholder = {
35713				element: function() {
35714
35715					var nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(),
35716						element = $( "<" + nodeName + ">", that.document[ 0 ] );
35717
35718						that._addClass( element, "ui-sortable-placeholder",
35719								className || that.currentItem[ 0 ].className )
35720							._removeClass( element, "ui-sortable-helper" );
35721
35722					if ( nodeName === "tbody" ) {
35723						that._createTrPlaceholder(
35724							that.currentItem.find( "tr" ).eq( 0 ),
35725							$( "<tr>", that.document[ 0 ] ).appendTo( element )
35726						);
35727					} else if ( nodeName === "tr" ) {
35728						that._createTrPlaceholder( that.currentItem, element );
35729					} else if ( nodeName === "img" ) {
35730						element.attr( "src", that.currentItem.attr( "src" ) );
35731					}
35732
35733					if ( !className ) {
35734						element.css( "visibility", "hidden" );
35735					}
35736
35737					return element;
35738				},
35739				update: function( container, p ) {
35740
35741					// 1. If a className is set as 'placeholder option, we don't force sizes -
35742					// the class is responsible for that
35743					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a
35744					// class name is specified
35745					if ( className && !o.forcePlaceholderSize ) {
35746						return;
35747					}
35748
35749					//If the element doesn't have a actual height by itself (without styles coming
35750					// from a stylesheet), it receives the inline height from the dragged item
35751					if ( !p.height() ) {
35752						p.height(
35753							that.currentItem.innerHeight() -
35754							parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) -
35755							parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) );
35756					}
35757					if ( !p.width() ) {
35758						p.width(
35759							that.currentItem.innerWidth() -
35760							parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) -
35761							parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) );
35762					}
35763				}
35764			};
35765		}
35766
35767		//Create the placeholder
35768		that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) );
35769
35770		//Append it after the actual current item
35771		that.currentItem.after( that.placeholder );
35772
35773		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
35774		o.placeholder.update( that, that.placeholder );
35775
35776	},
35777
35778	_createTrPlaceholder: function( sourceTr, targetTr ) {
35779		var that = this;
35780
35781		sourceTr.children().each( function() {
35782			$( "<td>&#160;</td>", that.document[ 0 ] )
35783				.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
35784				.appendTo( targetTr );
35785		} );
35786	},
35787
35788	_contactContainers: function( event ) {
35789		var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom,
35790			floating, axis,
35791			innermostContainer = null,
35792			innermostIndex = null;
35793
35794		// Get innermost container that intersects with item
35795		for ( i = this.containers.length - 1; i >= 0; i-- ) {
35796
35797			// Never consider a container that's located within the item itself
35798			if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) {
35799				continue;
35800			}
35801
35802			if ( this._intersectsWith( this.containers[ i ].containerCache ) ) {
35803
35804				// If we've already found a container and it's more "inner" than this, then continue
35805				if ( innermostContainer &&
35806						$.contains(
35807							this.containers[ i ].element[ 0 ],
35808							innermostContainer.element[ 0 ] ) ) {
35809					continue;
35810				}
35811
35812				innermostContainer = this.containers[ i ];
35813				innermostIndex = i;
35814
35815			} else {
35816
35817				// container doesn't intersect. trigger "out" event if necessary
35818				if ( this.containers[ i ].containerCache.over ) {
35819					this.containers[ i ]._trigger( "out", event, this._uiHash( this ) );
35820					this.containers[ i ].containerCache.over = 0;
35821				}
35822			}
35823
35824		}
35825
35826		// If no intersecting containers found, return
35827		if ( !innermostContainer ) {
35828			return;
35829		}
35830
35831		// Move the item into the container if it's not there already
35832		if ( this.containers.length === 1 ) {
35833			if ( !this.containers[ innermostIndex ].containerCache.over ) {
35834				this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
35835				this.containers[ innermostIndex ].containerCache.over = 1;
35836			}
35837		} else {
35838
35839			// When entering a new container, we will find the item with the least distance and
35840			// append our item near it
35841			dist = 10000;
35842			itemWithLeastDistance = null;
35843			floating = innermostContainer.floating || this._isFloating( this.currentItem );
35844			posProperty = floating ? "left" : "top";
35845			sizeProperty = floating ? "width" : "height";
35846			axis = floating ? "pageX" : "pageY";
35847
35848			for ( j = this.items.length - 1; j >= 0; j-- ) {
35849				if ( !$.contains(
35850						this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] )
35851				) {
35852					continue;
35853				}
35854				if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) {
35855					continue;
35856				}
35857
35858				cur = this.items[ j ].item.offset()[ posProperty ];
35859				nearBottom = false;
35860				if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
35861					nearBottom = true;
35862				}
35863
35864				if ( Math.abs( event[ axis ] - cur ) < dist ) {
35865					dist = Math.abs( event[ axis ] - cur );
35866					itemWithLeastDistance = this.items[ j ];
35867					this.direction = nearBottom ? "up" : "down";
35868				}
35869			}
35870
35871			//Check if dropOnEmpty is enabled
35872			if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) {
35873				return;
35874			}
35875
35876			if ( this.currentContainer === this.containers[ innermostIndex ] ) {
35877				if ( !this.currentContainer.containerCache.over ) {
35878					this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() );
35879					this.currentContainer.containerCache.over = 1;
35880				}
35881				return;
35882			}
35883
35884			itemWithLeastDistance ?
35885				this._rearrange( event, itemWithLeastDistance, null, true ) :
35886				this._rearrange( event, null, this.containers[ innermostIndex ].element, true );
35887			this._trigger( "change", event, this._uiHash() );
35888			this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) );
35889			this.currentContainer = this.containers[ innermostIndex ];
35890
35891			//Update the placeholder
35892			this.options.placeholder.update( this.currentContainer, this.placeholder );
35893
35894			this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
35895			this.containers[ innermostIndex ].containerCache.over = 1;
35896		}
35897
35898	},
35899
35900	_createHelper: function( event ) {
35901
35902		var o = this.options,
35903			helper = $.isFunction( o.helper ) ?
35904				$( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) :
35905				( o.helper === "clone" ? this.currentItem.clone() : this.currentItem );
35906
35907		//Add the helper to the DOM if that didn't happen already
35908		if ( !helper.parents( "body" ).length ) {
35909			$( o.appendTo !== "parent" ?
35910				o.appendTo :
35911				this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] );
35912		}
35913
35914		if ( helper[ 0 ] === this.currentItem[ 0 ] ) {
35915			this._storedCSS = {
35916				width: this.currentItem[ 0 ].style.width,
35917				height: this.currentItem[ 0 ].style.height,
35918				position: this.currentItem.css( "position" ),
35919				top: this.currentItem.css( "top" ),
35920				left: this.currentItem.css( "left" )
35921			};
35922		}
35923
35924		if ( !helper[ 0 ].style.width || o.forceHelperSize ) {
35925			helper.width( this.currentItem.width() );
35926		}
35927		if ( !helper[ 0 ].style.height || o.forceHelperSize ) {
35928			helper.height( this.currentItem.height() );
35929		}
35930
35931		return helper;
35932
35933	},
35934
35935	_adjustOffsetFromHelper: function( obj ) {
35936		if ( typeof obj === "string" ) {
35937			obj = obj.split( " " );
35938		}
35939		if ( $.isArray( obj ) ) {
35940			obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
35941		}
35942		if ( "left" in obj ) {
35943			this.offset.click.left = obj.left + this.margins.left;
35944		}
35945		if ( "right" in obj ) {
35946			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
35947		}
35948		if ( "top" in obj ) {
35949			this.offset.click.top = obj.top + this.margins.top;
35950		}
35951		if ( "bottom" in obj ) {
35952			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
35953		}
35954	},
35955
35956	_getParentOffset: function() {
35957
35958		//Get the offsetParent and cache its position
35959		this.offsetParent = this.helper.offsetParent();
35960		var po = this.offsetParent.offset();
35961
35962		// This is a special case where we need to modify a offset calculated on start, since the
35963		// following happened:
35964		// 1. The position of the helper is absolute, so it's position is calculated based on the
35965		// next positioned parent
35966		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
35967		// the document, which means that the scroll is included in the initial calculation of the
35968		// offset of the parent, and never recalculated upon drag
35969		if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] &&
35970				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
35971			po.left += this.scrollParent.scrollLeft();
35972			po.top += this.scrollParent.scrollTop();
35973		}
35974
35975		// This needs to be actually done for all browsers, since pageX/pageY includes this
35976		// information with an ugly IE fix
35977		if ( this.offsetParent[ 0 ] === this.document[ 0 ].body ||
35978				( this.offsetParent[ 0 ].tagName &&
35979				this.offsetParent[ 0 ].tagName.toLowerCase() === "html" && $.ui.ie ) ) {
35980			po = { top: 0, left: 0 };
35981		}
35982
35983		return {
35984			top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
35985			left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
35986		};
35987
35988	},
35989
35990	_getRelativeOffset: function() {
35991
35992		if ( this.cssPosition === "relative" ) {
35993			var p = this.currentItem.position();
35994			return {
35995				top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
35996					this.scrollParent.scrollTop(),
35997				left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
35998					this.scrollParent.scrollLeft()
35999			};
36000		} else {
36001			return { top: 0, left: 0 };
36002		}
36003
36004	},
36005
36006	_cacheMargins: function() {
36007		this.margins = {
36008			left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ),
36009			top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 )
36010		};
36011	},
36012
36013	_cacheHelperProportions: function() {
36014		this.helperProportions = {
36015			width: this.helper.outerWidth(),
36016			height: this.helper.outerHeight()
36017		};
36018	},
36019
36020	_setContainment: function() {
36021
36022		var ce, co, over,
36023			o = this.options;
36024		if ( o.containment === "parent" ) {
36025			o.containment = this.helper[ 0 ].parentNode;
36026		}
36027		if ( o.containment === "document" || o.containment === "window" ) {
36028			this.containment = [
36029				0 - this.offset.relative.left - this.offset.parent.left,
36030				0 - this.offset.relative.top - this.offset.parent.top,
36031				o.containment === "document" ?
36032					this.document.width() :
36033					this.window.width() - this.helperProportions.width - this.margins.left,
36034				( o.containment === "document" ?
36035					( this.document.height() || document.body.parentNode.scrollHeight ) :
36036					this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight
36037				) - this.helperProportions.height - this.margins.top
36038			];
36039		}
36040
36041		if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) {
36042			ce = $( o.containment )[ 0 ];
36043			co = $( o.containment ).offset();
36044			over = ( $( ce ).css( "overflow" ) !== "hidden" );
36045
36046			this.containment = [
36047				co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) +
36048					( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left,
36049				co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) +
36050					( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top,
36051				co.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
36052					( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) -
36053					( parseInt( $( ce ).css( "paddingRight" ), 10 ) || 0 ) -
36054					this.helperProportions.width - this.margins.left,
36055				co.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
36056					( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) -
36057					( parseInt( $( ce ).css( "paddingBottom" ), 10 ) || 0 ) -
36058					this.helperProportions.height - this.margins.top
36059			];
36060		}
36061
36062	},
36063
36064	_convertPositionTo: function( d, pos ) {
36065
36066		if ( !pos ) {
36067			pos = this.position;
36068		}
36069		var mod = d === "absolute" ? 1 : -1,
36070			scroll = this.cssPosition === "absolute" &&
36071				!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
36072				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
36073					this.offsetParent :
36074					this.scrollParent,
36075			scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
36076
36077		return {
36078			top: (
36079
36080				// The absolute mouse position
36081				pos.top	+
36082
36083				// Only for relative positioned nodes: Relative offset from element to offset parent
36084				this.offset.relative.top * mod +
36085
36086				// The offsetParent's offset without borders (offset + border)
36087				this.offset.parent.top * mod -
36088				( ( this.cssPosition === "fixed" ?
36089					-this.scrollParent.scrollTop() :
36090					( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod )
36091			),
36092			left: (
36093
36094				// The absolute mouse position
36095				pos.left +
36096
36097				// Only for relative positioned nodes: Relative offset from element to offset parent
36098				this.offset.relative.left * mod +
36099
36100				// The offsetParent's offset without borders (offset + border)
36101				this.offset.parent.left * mod	-
36102				( ( this.cssPosition === "fixed" ?
36103					-this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 :
36104					scroll.scrollLeft() ) * mod )
36105			)
36106		};
36107
36108	},
36109
36110	_generatePosition: function( event ) {
36111
36112		var top, left,
36113			o = this.options,
36114			pageX = event.pageX,
36115			pageY = event.pageY,
36116			scroll = this.cssPosition === "absolute" &&
36117				!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
36118				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
36119					this.offsetParent :
36120					this.scrollParent,
36121				scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
36122
36123		// This is another very weird special case that only happens for relative elements:
36124		// 1. If the css position is relative
36125		// 2. and the scroll parent is the document or similar to the offset parent
36126		// we have to refresh the relative offset during the scroll so there are no jumps
36127		if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
36128				this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) {
36129			this.offset.relative = this._getRelativeOffset();
36130		}
36131
36132		/*
36133		 * - Position constraining -
36134		 * Constrain the position to a mix of grid, containment.
36135		 */
36136
36137		if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options
36138
36139			if ( this.containment ) {
36140				if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) {
36141					pageX = this.containment[ 0 ] + this.offset.click.left;
36142				}
36143				if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) {
36144					pageY = this.containment[ 1 ] + this.offset.click.top;
36145				}
36146				if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) {
36147					pageX = this.containment[ 2 ] + this.offset.click.left;
36148				}
36149				if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) {
36150					pageY = this.containment[ 3 ] + this.offset.click.top;
36151				}
36152			}
36153
36154			if ( o.grid ) {
36155				top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) /
36156					o.grid[ 1 ] ) * o.grid[ 1 ];
36157				pageY = this.containment ?
36158					( ( top - this.offset.click.top >= this.containment[ 1 ] &&
36159						top - this.offset.click.top <= this.containment[ 3 ] ) ?
36160							top :
36161							( ( top - this.offset.click.top >= this.containment[ 1 ] ) ?
36162								top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) :
36163								top;
36164
36165				left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) /
36166					o.grid[ 0 ] ) * o.grid[ 0 ];
36167				pageX = this.containment ?
36168					( ( left - this.offset.click.left >= this.containment[ 0 ] &&
36169						left - this.offset.click.left <= this.containment[ 2 ] ) ?
36170							left :
36171							( ( left - this.offset.click.left >= this.containment[ 0 ] ) ?
36172								left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) :
36173								left;
36174			}
36175
36176		}
36177
36178		return {
36179			top: (
36180
36181				// The absolute mouse position
36182				pageY -
36183
36184				// Click offset (relative to the element)
36185				this.offset.click.top -
36186
36187				// Only for relative positioned nodes: Relative offset from element to offset parent
36188				this.offset.relative.top -
36189
36190				// The offsetParent's offset without borders (offset + border)
36191				this.offset.parent.top +
36192				( ( this.cssPosition === "fixed" ?
36193					-this.scrollParent.scrollTop() :
36194					( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) )
36195			),
36196			left: (
36197
36198				// The absolute mouse position
36199				pageX -
36200
36201				// Click offset (relative to the element)
36202				this.offset.click.left -
36203
36204				// Only for relative positioned nodes: Relative offset from element to offset parent
36205				this.offset.relative.left -
36206
36207				// The offsetParent's offset without borders (offset + border)
36208				this.offset.parent.left +
36209				( ( this.cssPosition === "fixed" ?
36210					-this.scrollParent.scrollLeft() :
36211					scrollIsRootNode ? 0 : scroll.scrollLeft() ) )
36212			)
36213		};
36214
36215	},
36216
36217	_rearrange: function( event, i, a, hardRefresh ) {
36218
36219		a ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) :
36220			i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ],
36221				( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) );
36222
36223		//Various things done here to improve the performance:
36224		// 1. we create a setTimeout, that calls refreshPositions
36225		// 2. on the instance, we have a counter variable, that get's higher after every append
36226		// 3. on the local scope, we copy the counter variable, and check in the timeout,
36227		// if it's still the same
36228		// 4. this lets only the last addition to the timeout stack through
36229		this.counter = this.counter ? ++this.counter : 1;
36230		var counter = this.counter;
36231
36232		this._delay( function() {
36233			if ( counter === this.counter ) {
36234
36235				//Precompute after each DOM insertion, NOT on mousemove
36236				this.refreshPositions( !hardRefresh );
36237			}
36238		} );
36239
36240	},
36241
36242	_clear: function( event, noPropagation ) {
36243
36244		this.reverting = false;
36245
36246		// We delay all events that have to be triggered to after the point where the placeholder
36247		// has been removed and everything else normalized again
36248		var i,
36249			delayedTriggers = [];
36250
36251		// We first have to update the dom position of the actual currentItem
36252		// Note: don't do it if the current item is already removed (by a user), or it gets
36253		// reappended (see #4088)
36254		if ( !this._noFinalSort && this.currentItem.parent().length ) {
36255			this.placeholder.before( this.currentItem );
36256		}
36257		this._noFinalSort = null;
36258
36259		if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) {
36260			for ( i in this._storedCSS ) {
36261				if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) {
36262					this._storedCSS[ i ] = "";
36263				}
36264			}
36265			this.currentItem.css( this._storedCSS );
36266			this._removeClass( this.currentItem, "ui-sortable-helper" );
36267		} else {
36268			this.currentItem.show();
36269		}
36270
36271		if ( this.fromOutside && !noPropagation ) {
36272			delayedTriggers.push( function( event ) {
36273				this._trigger( "receive", event, this._uiHash( this.fromOutside ) );
36274			} );
36275		}
36276		if ( ( this.fromOutside ||
36277				this.domPosition.prev !==
36278				this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] ||
36279				this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) {
36280
36281			// Trigger update callback if the DOM position has changed
36282			delayedTriggers.push( function( event ) {
36283				this._trigger( "update", event, this._uiHash() );
36284			} );
36285		}
36286
36287		// Check if the items Container has Changed and trigger appropriate
36288		// events.
36289		if ( this !== this.currentContainer ) {
36290			if ( !noPropagation ) {
36291				delayedTriggers.push( function( event ) {
36292					this._trigger( "remove", event, this._uiHash() );
36293				} );
36294				delayedTriggers.push( ( function( c ) {
36295					return function( event ) {
36296						c._trigger( "receive", event, this._uiHash( this ) );
36297					};
36298				} ).call( this, this.currentContainer ) );
36299				delayedTriggers.push( ( function( c ) {
36300					return function( event ) {
36301						c._trigger( "update", event, this._uiHash( this ) );
36302					};
36303				} ).call( this, this.currentContainer ) );
36304			}
36305		}
36306
36307		//Post events to containers
36308		function delayEvent( type, instance, container ) {
36309			return function( event ) {
36310				container._trigger( type, event, instance._uiHash( instance ) );
36311			};
36312		}
36313		for ( i = this.containers.length - 1; i >= 0; i-- ) {
36314			if ( !noPropagation ) {
36315				delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
36316			}
36317			if ( this.containers[ i ].containerCache.over ) {
36318				delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
36319				this.containers[ i ].containerCache.over = 0;
36320			}
36321		}
36322
36323		//Do what was originally in plugins
36324		if ( this.storedCursor ) {
36325			this.document.find( "body" ).css( "cursor", this.storedCursor );
36326			this.storedStylesheet.remove();
36327		}
36328		if ( this._storedOpacity ) {
36329			this.helper.css( "opacity", this._storedOpacity );
36330		}
36331		if ( this._storedZIndex ) {
36332			this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex );
36333		}
36334
36335		this.dragging = false;
36336
36337		if ( !noPropagation ) {
36338			this._trigger( "beforeStop", event, this._uiHash() );
36339		}
36340
36341		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
36342		// it unbinds ALL events from the original node!
36343		this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
36344
36345		if ( !this.cancelHelperRemoval ) {
36346			if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
36347				this.helper.remove();
36348			}
36349			this.helper = null;
36350		}
36351
36352		if ( !noPropagation ) {
36353			for ( i = 0; i < delayedTriggers.length; i++ ) {
36354
36355				// Trigger all delayed events
36356				delayedTriggers[ i ].call( this, event );
36357			}
36358			this._trigger( "stop", event, this._uiHash() );
36359		}
36360
36361		this.fromOutside = false;
36362		return !this.cancelHelperRemoval;
36363
36364	},
36365
36366	_trigger: function() {
36367		if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) {
36368			this.cancel();
36369		}
36370	},
36371
36372	_uiHash: function( _inst ) {
36373		var inst = _inst || this;
36374		return {
36375			helper: inst.helper,
36376			placeholder: inst.placeholder || $( [] ),
36377			position: inst.position,
36378			originalPosition: inst.originalPosition,
36379			offset: inst.positionAbs,
36380			item: inst.currentItem,
36381			sender: _inst ? _inst.element : null
36382		};
36383	}
36384
36385} );
36386
36387} ) );
36388
36389
36390
36391
36392
36393
36394/*!
36395 * jQuery UI Spinner 1.12.1
36396 * http://jqueryui.com
36397 *
36398 * Copyright jQuery Foundation and other contributors
36399 * Released under the MIT license.
36400 * http://jquery.org/license
36401 */
36402
36403//>>label: Spinner
36404//>>group: Widgets
36405//>>description: Displays buttons to easily input numbers via the keyboard or mouse.
36406//>>docs: http://api.jqueryui.com/spinner/
36407//>>demos: http://jqueryui.com/spinner/
36408//>>css.structure: ../../themes/base/core.css
36409//>>css.structure: ../../themes/base/spinner.css
36410//>>css.theme: ../../themes/base/theme.css
36411
36412( function( factory ) {
36413	if ( typeof define === "function" && define.amd ) {
36414
36415		// AMD. Register as an anonymous module.
36416		define( [
36417			"jquery",
36418			"./button",
36419			"../version",
36420			"../keycode",
36421			"../safe-active-element",
36422			"../widget"
36423		], factory );
36424	} else {
36425
36426		// Browser globals
36427		factory( jQuery );
36428	}
36429}( function( $ ) {
vendor: 13,364 bytes, lines 36429-36965
36429
36430
36431function spinnerModifer( fn ) {
36432	return function() {
36433		var previous = this.element.val();
36434		fn.apply( this, arguments );
36435		this._refresh();
36436		if ( previous !== this.element.val() ) {
36437			this._trigger( "change" );
36438		}
36439	};
36440}
36441
36442$.widget( "ui.spinner", {
36443	version: "1.12.1",
36444	defaultElement: "<input>",
36445	widgetEventPrefix: "spin",
36446	options: {
36447		classes: {
36448			"ui-spinner": "ui-corner-all",
36449			"ui-spinner-down": "ui-corner-br",
36450			"ui-spinner-up": "ui-corner-tr"
36451		},
36452		culture: null,
36453		icons: {
36454			down: "ui-icon-triangle-1-s",
36455			up: "ui-icon-triangle-1-n"
36456		},
36457		incremental: true,
36458		max: null,
36459		min: null,
36460		numberFormat: null,
36461		page: 10,
36462		step: 1,
36463
36464		change: null,
36465		spin: null,
36466		start: null,
36467		stop: null
36468	},
36469
36470	_create: function() {
36471
36472		// handle string values that need to be parsed
36473		this._setOption( "max", this.options.max );
36474		this._setOption( "min", this.options.min );
36475		this._setOption( "step", this.options.step );
36476
36477		// Only format if there is a value, prevents the field from being marked
36478		// as invalid in Firefox, see #9573.
36479		if ( this.value() !== "" ) {
36480
36481			// Format the value, but don't constrain.
36482			this._value( this.element.val(), true );
36483		}
36484
36485		this._draw();
36486		this._on( this._events );
36487		this._refresh();
36488
36489		// Turning off autocomplete prevents the browser from remembering the
36490		// value when navigating through history, so we re-enable autocomplete
36491		// if the page is unloaded before the widget is destroyed. #7790
36492		this._on( this.window, {
36493			beforeunload: function() {
36494				this.element.removeAttr( "autocomplete" );
36495			}
36496		} );
36497	},
36498
36499	_getCreateOptions: function() {
36500		var options = this._super();
36501		var element = this.element;
36502
36503		$.each( [ "min", "max", "step" ], function( i, option ) {
36504			var value = element.attr( option );
36505			if ( value != null && value.length ) {
36506				options[ option ] = value;
36507			}
36508		} );
36509
36510		return options;
36511	},
36512
36513	_events: {
36514		keydown: function( event ) {
36515			if ( this._start( event ) && this._keydown( event ) ) {
36516				event.preventDefault();
36517			}
36518		},
36519		keyup: "_stop",
36520		focus: function() {
36521			this.previous = this.element.val();
36522		},
36523		blur: function( event ) {
36524			if ( this.cancelBlur ) {
36525				delete this.cancelBlur;
36526				return;
36527			}
36528
36529			this._stop();
36530			this._refresh();
36531			if ( this.previous !== this.element.val() ) {
36532				this._trigger( "change", event );
36533			}
36534		},
36535		mousewheel: function( event, delta ) {
36536			if ( !delta ) {
36537				return;
36538			}
36539			if ( !this.spinning && !this._start( event ) ) {
36540				return false;
36541			}
36542
36543			this._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event );
36544			clearTimeout( this.mousewheelTimer );
36545			this.mousewheelTimer = this._delay( function() {
36546				if ( this.spinning ) {
36547					this._stop( event );
36548				}
36549			}, 100 );
36550			event.preventDefault();
36551		},
36552		"mousedown .ui-spinner-button": function( event ) {
36553			var previous;
36554
36555			// We never want the buttons to have focus; whenever the user is
36556			// interacting with the spinner, the focus should be on the input.
36557			// If the input is focused then this.previous is properly set from
36558			// when the input first received focus. If the input is not focused
36559			// then we need to set this.previous based on the value before spinning.
36560			previous = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ?
36561				this.previous : this.element.val();
36562			function checkFocus() {
36563				var isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] );
36564				if ( !isActive ) {
36565					this.element.trigger( "focus" );
36566					this.previous = previous;
36567
36568					// support: IE
36569					// IE sets focus asynchronously, so we need to check if focus
36570					// moved off of the input because the user clicked on the button.
36571					this._delay( function() {
36572						this.previous = previous;
36573					} );
36574				}
36575			}
36576
36577			// Ensure focus is on (or stays on) the text field
36578			event.preventDefault();
36579			checkFocus.call( this );
36580
36581			// Support: IE
36582			// IE doesn't prevent moving focus even with event.preventDefault()
36583			// so we set a flag to know when we should ignore the blur event
36584			// and check (again) if focus moved off of the input.
36585			this.cancelBlur = true;
36586			this._delay( function() {
36587				delete this.cancelBlur;
36588				checkFocus.call( this );
36589			} );
36590
36591			if ( this._start( event ) === false ) {
36592				return;
36593			}
36594
36595			this._repeat( null, $( event.currentTarget )
36596				.hasClass( "ui-spinner-up" ) ? 1 : -1, event );
36597		},
36598		"mouseup .ui-spinner-button": "_stop",
36599		"mouseenter .ui-spinner-button": function( event ) {
36600
36601			// button will add ui-state-active if mouse was down while mouseleave and kept down
36602			if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
36603				return;
36604			}
36605
36606			if ( this._start( event ) === false ) {
36607				return false;
36608			}
36609			this._repeat( null, $( event.currentTarget )
36610				.hasClass( "ui-spinner-up" ) ? 1 : -1, event );
36611		},
36612
36613		// TODO: do we really want to consider this a stop?
36614		// shouldn't we just stop the repeater and wait until mouseup before
36615		// we trigger the stop event?
36616		"mouseleave .ui-spinner-button": "_stop"
36617	},
36618
36619	// Support mobile enhanced option and make backcompat more sane
36620	_enhance: function() {
36621		this.uiSpinner = this.element
36622			.attr( "autocomplete", "off" )
36623			.wrap( "<span>" )
36624			.parent()
36625
36626				// Add buttons
36627				.append(
36628					"<a></a><a></a>"
36629				);
36630	},
36631
36632	_draw: function() {
36633		this._enhance();
36634
36635		this._addClass( this.uiSpinner, "ui-spinner", "ui-widget ui-widget-content" );
36636		this._addClass( "ui-spinner-input" );
36637
36638		this.element.attr( "role", "spinbutton" );
36639
36640		// Button bindings
36641		this.buttons = this.uiSpinner.children( "a" )
36642			.attr( "tabIndex", -1 )
36643			.attr( "aria-hidden", true )
36644			.button( {
36645				classes: {
36646					"ui-button": ""
36647				}
36648			} );
36649
36650		// TODO: Right now button does not support classes this is already updated in button PR
36651		this._removeClass( this.buttons, "ui-corner-all" );
36652
36653		this._addClass( this.buttons.first(), "ui-spinner-button ui-spinner-up" );
36654		this._addClass( this.buttons.last(), "ui-spinner-button ui-spinner-down" );
36655		this.buttons.first().button( {
36656			"icon": this.options.icons.up,
36657			"showLabel": false
36658		} );
36659		this.buttons.last().button( {
36660			"icon": this.options.icons.down,
36661			"showLabel": false
36662		} );
36663
36664		// IE 6 doesn't understand height: 50% for the buttons
36665		// unless the wrapper has an explicit height
36666		if ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) &&
36667				this.uiSpinner.height() > 0 ) {
36668			this.uiSpinner.height( this.uiSpinner.height() );
36669		}
36670	},
36671
36672	_keydown: function( event ) {
36673		var options = this.options,
36674			keyCode = $.ui.keyCode;
36675
36676		switch ( event.keyCode ) {
36677		case keyCode.UP:
36678			this._repeat( null, 1, event );
36679			return true;
36680		case keyCode.DOWN:
36681			this._repeat( null, -1, event );
36682			return true;
36683		case keyCode.PAGE_UP:
36684			this._repeat( null, options.page, event );
36685			return true;
36686		case keyCode.PAGE_DOWN:
36687			this._repeat( null, -options.page, event );
36688			return true;
36689		}
36690
36691		return false;
36692	},
36693
36694	_start: function( event ) {
36695		if ( !this.spinning && this._trigger( "start", event ) === false ) {
36696			return false;
36697		}
36698
36699		if ( !this.counter ) {
36700			this.counter = 1;
36701		}
36702		this.spinning = true;
36703		return true;
36704	},
36705
36706	_repeat: function( i, steps, event ) {
36707		i = i || 500;
36708
36709		clearTimeout( this.timer );
36710		this.timer = this._delay( function() {
36711			this._repeat( 40, steps, event );
36712		}, i );
36713
36714		this._spin( steps * this.options.step, event );
36715	},
36716
36717	_spin: function( step, event ) {
36718		var value = this.value() || 0;
36719
36720		if ( !this.counter ) {
36721			this.counter = 1;
36722		}
36723
36724		value = this._adjustValue( value + step * this._increment( this.counter ) );
36725
36726		if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false ) {
36727			this._value( value );
36728			this.counter++;
36729		}
36730	},
36731
36732	_increment: function( i ) {
36733		var incremental = this.options.incremental;
36734
36735		if ( incremental ) {
36736			return $.isFunction( incremental ) ?
36737				incremental( i ) :
36738				Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );
36739		}
36740
36741		return 1;
36742	},
36743
36744	_precision: function() {
36745		var precision = this._precisionOf( this.options.step );
36746		if ( this.options.min !== null ) {
36747			precision = Math.max( precision, this._precisionOf( this.options.min ) );
36748		}
36749		return precision;
36750	},
36751
36752	_precisionOf: function( num ) {
36753		var str = num.toString(),
36754			decimal = str.indexOf( "." );
36755		return decimal === -1 ? 0 : str.length - decimal - 1;
36756	},
36757
36758	_adjustValue: function( value ) {
36759		var base, aboveMin,
36760			options = this.options;
36761
36762		// Make sure we're at a valid step
36763		// - find out where we are relative to the base (min or 0)
36764		base = options.min !== null ? options.min : 0;
36765		aboveMin = value - base;
36766
36767		// - round to the nearest step
36768		aboveMin = Math.round( aboveMin / options.step ) * options.step;
36769
36770		// - rounding is based on 0, so adjust back to our base
36771		value = base + aboveMin;
36772
36773		// Fix precision from bad JS floating point math
36774		value = parseFloat( value.toFixed( this._precision() ) );
36775
36776		// Clamp the value
36777		if ( options.max !== null && value > options.max ) {
36778			return options.max;
36779		}
36780		if ( options.min !== null && value < options.min ) {
36781			return options.min;
36782		}
36783
36784		return value;
36785	},
36786
36787	_stop: function( event ) {
36788		if ( !this.spinning ) {
36789			return;
36790		}
36791
36792		clearTimeout( this.timer );
36793		clearTimeout( this.mousewheelTimer );
36794		this.counter = 0;
36795		this.spinning = false;
36796		this._trigger( "stop", event );
36797	},
36798
36799	_setOption: function( key, value ) {
36800		var prevValue, first, last;
36801
36802		if ( key === "culture" || key === "numberFormat" ) {
36803			prevValue = this._parse( this.element.val() );
36804			this.options[ key ] = value;
36805			this.element.val( this._format( prevValue ) );
36806			return;
36807		}
36808
36809		if ( key === "max" || key === "min" || key === "step" ) {
36810			if ( typeof value === "string" ) {
36811				value = this._parse( value );
36812			}
36813		}
36814		if ( key === "icons" ) {
36815			first = this.buttons.first().find( ".ui-icon" );
36816			this._removeClass( first, null, this.options.icons.up );
36817			this._addClass( first, null, value.up );
36818			last = this.buttons.last().find( ".ui-icon" );
36819			this._removeClass( last, null, this.options.icons.down );
36820			this._addClass( last, null, value.down );
36821		}
36822
36823		this._super( key, value );
36824	},
36825
36826	_setOptionDisabled: function( value ) {
36827		this._super( value );
36828
36829		this._toggleClass( this.uiSpinner, null, "ui-state-disabled", !!value );
36830		this.element.prop( "disabled", !!value );
36831		this.buttons.button( value ? "disable" : "enable" );
36832	},
36833
36834	_setOptions: spinnerModifer( function( options ) {
36835		this._super( options );
36836	} ),
36837
36838	_parse: function( val ) {
36839		if ( typeof val === "string" && val !== "" ) {
36840			val = window.Globalize && this.options.numberFormat ?
36841				Globalize.parseFloat( val, 10, this.options.culture ) : +val;
36842		}
36843		return val === "" || isNaN( val ) ? null : val;
36844	},
36845
36846	_format: function( value ) {
36847		if ( value === "" ) {
36848			return "";
36849		}
36850		return window.Globalize && this.options.numberFormat ?
36851			Globalize.format( value, this.options.numberFormat, this.options.culture ) :
36852			value;
36853	},
36854
36855	_refresh: function() {
36856		this.element.attr( {
36857			"aria-valuemin": this.options.min,
36858			"aria-valuemax": this.options.max,
36859
36860			// TODO: what should we do with values that can't be parsed?
36861			"aria-valuenow": this._parse( this.element.val() )
36862		} );
36863	},
36864
36865	isValid: function() {
36866		var value = this.value();
36867
36868		// Null is invalid
36869		if ( value === null ) {
36870			return false;
36871		}
36872
36873		// If value gets adjusted, it's invalid
36874		return value === this._adjustValue( value );
36875	},
36876
36877	// Update the value without triggering change
36878	_value: function( value, allowAny ) {
36879		var parsed;
36880		if ( value !== "" ) {
36881			parsed = this._parse( value );
36882			if ( parsed !== null ) {
36883				if ( !allowAny ) {
36884					parsed = this._adjustValue( parsed );
36885				}
36886				value = this._format( parsed );
36887			}
36888		}
36889		this.element.val( value );
36890		this._refresh();
36891	},
36892
36893	_destroy: function() {
36894		this.element
36895			.prop( "disabled", false )
36896			.removeAttr( "autocomplete role aria-valuemin aria-valuemax aria-valuenow" );
36897
36898		this.uiSpinner.replaceWith( this.element );
36899	},
36900
36901	stepUp: spinnerModifer( function( steps ) {
36902		this._stepUp( steps );
36903	} ),
36904	_stepUp: function( steps ) {
36905		if ( this._start() ) {
36906			this._spin( ( steps || 1 ) * this.options.step );
36907			this._stop();
36908		}
36909	},
36910
36911	stepDown: spinnerModifer( function( steps ) {
36912		this._stepDown( steps );
36913	} ),
36914	_stepDown: function( steps ) {
36915		if ( this._start() ) {
36916			this._spin( ( steps || 1 ) * -this.options.step );
36917			this._stop();
36918		}
36919	},
36920
36921	pageUp: spinnerModifer( function( pages ) {
36922		this._stepUp( ( pages || 1 ) * this.options.page );
36923	} ),
36924
36925	pageDown: spinnerModifer( function( pages ) {
36926		this._stepDown( ( pages || 1 ) * this.options.page );
36927	} ),
36928
36929	value: function( newVal ) {
36930		if ( !arguments.length ) {
36931			return this._parse( this.element.val() );
36932		}
36933		spinnerModifer( this._value ).call( this, newVal );
36934	},
36935
36936	widget: function() {
36937		return this.uiSpinner;
36938	}
36939} );
36940
36941// DEPRECATED
36942// TODO: switch return back to widget declaration at top of file when this is removed
36943if ( $.uiBackCompat !== false ) {
36944
36945	// Backcompat for spinner html extension points
36946	$.widget( "ui.spinner", $.ui.spinner, {
36947		_enhance: function() {
36948			this.uiSpinner = this.element
36949				.attr( "autocomplete", "off" )
36950				.wrap( this._uiSpinnerHtml() )
36951				.parent()
36952
36953					// Add buttons
36954					.append( this._buttonHtml() );
36955		},
36956		_uiSpinnerHtml: function() {
36957			return "<span>";
36958		},
36959
36960		_buttonHtml: function() {
36961			return "<a></a><a></a>";
36962		}
36963	} );
36964}
36965
36966return $.ui.spinner;
36967
36968} ) );
36969
36970
36971
36972
36973
36974
36975
36976/*!
36977 * jQuery UI Tabs 1.12.1
36978 * http://jqueryui.com
36979 *
36980 * Copyright jQuery Foundation and other contributors
36981 * Released under the MIT license.
36982 * http://jquery.org/license
36983 */
36984
36985//>>label: Tabs
36986//>>group: Widgets
36987//>>description: Transforms a set of container elements into a tab structure.
36988//>>docs: http://api.jqueryui.com/tabs/
36989//>>demos: http://jqueryui.com/tabs/
36990//>>css.structure: ../../themes/base/core.css
36991//>>css.structure: ../../themes/base/tabs.css
36992//>>css.theme: ../../themes/base/theme.css
36993
36994( function( factory ) {
36995	if ( typeof define === "function" && define.amd ) {
36996
36997		// AMD. Register as an anonymous module.
36998		define( [
36999			"jquery",
37000			"../escape-selector",
37001			"../keycode",
37002			"../safe-active-element",
37003			"../unique-id",
37004			"../version",
37005			"../widget"
37006		], factory );
37007	} else {
37008
37009		// Browser globals
37010		factory( jQuery );
37011	}
37012}( function( $ ) {
vendor: 22,774 bytes, lines 37012-37896
37012
37013
37014$.widget( "ui.tabs", {
37015	version: "1.12.1",
37016	delay: 300,
37017	options: {
37018		active: null,
37019		classes: {
37020			"ui-tabs": "ui-corner-all",
37021			"ui-tabs-nav": "ui-corner-all",
37022			"ui-tabs-panel": "ui-corner-bottom",
37023			"ui-tabs-tab": "ui-corner-top"
37024		},
37025		collapsible: false,
37026		event: "click",
37027		heightStyle: "content",
37028		hide: null,
37029		show: null,
37030
37031		// Callbacks
37032		activate: null,
37033		beforeActivate: null,
37034		beforeLoad: null,
37035		load: null
37036	},
37037
37038	_isLocal: ( function() {
37039		var rhash = /#.*$/;
37040
37041		return function( anchor ) {
37042			var anchorUrl, locationUrl;
37043
37044			anchorUrl = anchor.href.replace( rhash, "" );
37045			locationUrl = location.href.replace( rhash, "" );
37046
37047			// Decoding may throw an error if the URL isn't UTF-8 (#9518)
37048			try {
37049				anchorUrl = decodeURIComponent( anchorUrl );
37050			} catch ( error ) {}
37051			try {
37052				locationUrl = decodeURIComponent( locationUrl );
37053			} catch ( error ) {}
37054
37055			return anchor.hash.length > 1 && anchorUrl === locationUrl;
37056		};
37057	} )(),
37058
37059	_create: function() {
37060		var that = this,
37061			options = this.options;
37062
37063		this.running = false;
37064
37065		this._addClass( "ui-tabs", "ui-widget ui-widget-content" );
37066		this._toggleClass( "ui-tabs-collapsible", null, options.collapsible );
37067
37068		this._processTabs();
37069		options.active = this._initialActive();
37070
37071		// Take disabling tabs via class attribute from HTML
37072		// into account and update option properly.
37073		if ( $.isArray( options.disabled ) ) {
37074			options.disabled = $.unique( options.disabled.concat(
37075				$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
37076					return that.tabs.index( li );
37077				} )
37078			) ).sort();
37079		}
37080
37081		// Check for length avoids error when initializing empty list
37082		if ( this.options.active !== false && this.anchors.length ) {
37083			this.active = this._findActive( options.active );
37084		} else {
37085			this.active = $();
37086		}
37087
37088		this._refresh();
37089
37090		if ( this.active.length ) {
37091			this.load( options.active );
37092		}
37093	},
37094
37095	_initialActive: function() {
37096		var active = this.options.active,
37097			collapsible = this.options.collapsible,
37098			locationHash = location.hash.substring( 1 );
37099
37100		if ( active === null ) {
37101
37102			// check the fragment identifier in the URL
37103			if ( locationHash ) {
37104				this.tabs.each( function( i, tab ) {
37105					if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
37106						active = i;
37107						return false;
37108					}
37109				} );
37110			}
37111
37112			// Check for a tab marked active via a class
37113			if ( active === null ) {
37114				active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
37115			}
37116
37117			// No active tab, set to false
37118			if ( active === null || active === -1 ) {
37119				active = this.tabs.length ? 0 : false;
37120			}
37121		}
37122
37123		// Handle numbers: negative, out of range
37124		if ( active !== false ) {
37125			active = this.tabs.index( this.tabs.eq( active ) );
37126			if ( active === -1 ) {
37127				active = collapsible ? false : 0;
37128			}
37129		}
37130
37131		// Don't allow collapsible: false and active: false
37132		if ( !collapsible && active === false && this.anchors.length ) {
37133			active = 0;
37134		}
37135
37136		return active;
37137	},
37138
37139	_getCreateEventData: function() {
37140		return {
37141			tab: this.active,
37142			panel: !this.active.length ? $() : this._getPanelForTab( this.active )
37143		};
37144	},
37145
37146	_tabKeydown: function( event ) {
37147		var focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( "li" ),
37148			selectedIndex = this.tabs.index( focusedTab ),
37149			goingForward = true;
37150
37151		if ( this._handlePageNav( event ) ) {
37152			return;
37153		}
37154
37155		switch ( event.keyCode ) {
37156		case $.ui.keyCode.RIGHT:
37157		case $.ui.keyCode.DOWN:
37158			selectedIndex++;
37159			break;
37160		case $.ui.keyCode.UP:
37161		case $.ui.keyCode.LEFT:
37162			goingForward = false;
37163			selectedIndex--;
37164			break;
37165		case $.ui.keyCode.END:
37166			selectedIndex = this.anchors.length - 1;
37167			break;
37168		case $.ui.keyCode.HOME:
37169			selectedIndex = 0;
37170			break;
37171		case $.ui.keyCode.SPACE:
37172
37173			// Activate only, no collapsing
37174			event.preventDefault();
37175			clearTimeout( this.activating );
37176			this._activate( selectedIndex );
37177			return;
37178		case $.ui.keyCode.ENTER:
37179
37180			// Toggle (cancel delayed activation, allow collapsing)
37181			event.preventDefault();
37182			clearTimeout( this.activating );
37183
37184			// Determine if we should collapse or activate
37185			this._activate( selectedIndex === this.options.active ? false : selectedIndex );
37186			return;
37187		default:
37188			return;
37189		}
37190
37191		// Focus the appropriate tab, based on which key was pressed
37192		event.preventDefault();
37193		clearTimeout( this.activating );
37194		selectedIndex = this._focusNextTab( selectedIndex, goingForward );
37195
37196		// Navigating with control/command key will prevent automatic activation
37197		if ( !event.ctrlKey && !event.metaKey ) {
37198
37199			// Update aria-selected immediately so that AT think the tab is already selected.
37200			// Otherwise AT may confuse the user by stating that they need to activate the tab,
37201			// but the tab will already be activated by the time the announcement finishes.
37202			focusedTab.attr( "aria-selected", "false" );
37203			this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
37204
37205			this.activating = this._delay( function() {
37206				this.option( "active", selectedIndex );
37207			}, this.delay );
37208		}
37209	},
37210
37211	_panelKeydown: function( event ) {
37212		if ( this._handlePageNav( event ) ) {
37213			return;
37214		}
37215
37216		// Ctrl+up moves focus to the current tab
37217		if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
37218			event.preventDefault();
37219			this.active.trigger( "focus" );
37220		}
37221	},
37222
37223	// Alt+page up/down moves focus to the previous/next tab (and activates)
37224	_handlePageNav: function( event ) {
37225		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
37226			this._activate( this._focusNextTab( this.options.active - 1, false ) );
37227			return true;
37228		}
37229		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
37230			this._activate( this._focusNextTab( this.options.active + 1, true ) );
37231			return true;
37232		}
37233	},
37234
37235	_findNextTab: function( index, goingForward ) {
37236		var lastTabIndex = this.tabs.length - 1;
37237
37238		function constrain() {
37239			if ( index > lastTabIndex ) {
37240				index = 0;
37241			}
37242			if ( index < 0 ) {
37243				index = lastTabIndex;
37244			}
37245			return index;
37246		}
37247
37248		while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
37249			index = goingForward ? index + 1 : index - 1;
37250		}
37251
37252		return index;
37253	},
37254
37255	_focusNextTab: function( index, goingForward ) {
37256		index = this._findNextTab( index, goingForward );
37257		this.tabs.eq( index ).trigger( "focus" );
37258		return index;
37259	},
37260
37261	_setOption: function( key, value ) {
37262		if ( key === "active" ) {
37263
37264			// _activate() will handle invalid values and update this.options
37265			this._activate( value );
37266			return;
37267		}
37268
37269		this._super( key, value );
37270
37271		if ( key === "collapsible" ) {
37272			this._toggleClass( "ui-tabs-collapsible", null, value );
37273
37274			// Setting collapsible: false while collapsed; open first panel
37275			if ( !value && this.options.active === false ) {
37276				this._activate( 0 );
37277			}
37278		}
37279
37280		if ( key === "event" ) {
37281			this._setupEvents( value );
37282		}
37283
37284		if ( key === "heightStyle" ) {
37285			this._setupHeightStyle( value );
37286		}
37287	},
37288
37289	_sanitizeSelector: function( hash ) {
37290		return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
37291	},
37292
37293	refresh: function() {
37294		var options = this.options,
37295			lis = this.tablist.children( ":has(a[href])" );
37296
37297		// Get disabled tabs from class attribute from HTML
37298		// this will get converted to a boolean if needed in _refresh()
37299		options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
37300			return lis.index( tab );
37301		} );
37302
37303		this._processTabs();
37304
37305		// Was collapsed or no tabs
37306		if ( options.active === false || !this.anchors.length ) {
37307			options.active = false;
37308			this.active = $();
37309
37310		// was active, but active tab is gone
37311		} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
37312
37313			// all remaining tabs are disabled
37314			if ( this.tabs.length === options.disabled.length ) {
37315				options.active = false;
37316				this.active = $();
37317
37318			// activate previous tab
37319			} else {
37320				this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
37321			}
37322
37323		// was active, active tab still exists
37324		} else {
37325
37326			// make sure active index is correct
37327			options.active = this.tabs.index( this.active );
37328		}
37329
37330		this._refresh();
37331	},
37332
37333	_refresh: function() {
37334		this._setOptionDisabled( this.options.disabled );
37335		this._setupEvents( this.options.event );
37336		this._setupHeightStyle( this.options.heightStyle );
37337
37338		this.tabs.not( this.active ).attr( {
37339			"aria-selected": "false",
37340			"aria-expanded": "false",
37341			tabIndex: -1
37342		} );
37343		this.panels.not( this._getPanelForTab( this.active ) )
37344			.hide()
37345			.attr( {
37346				"aria-hidden": "true"
37347			} );
37348
37349		// Make sure one tab is in the tab order
37350		if ( !this.active.length ) {
37351			this.tabs.eq( 0 ).attr( "tabIndex", 0 );
37352		} else {
37353			this.active
37354				.attr( {
37355					"aria-selected": "true",
37356					"aria-expanded": "true",
37357					tabIndex: 0
37358				} );
37359			this._addClass( this.active, "ui-tabs-active", "ui-state-active" );
37360			this._getPanelForTab( this.active )
37361				.show()
37362				.attr( {
37363					"aria-hidden": "false"
37364				} );
37365		}
37366	},
37367
37368	_processTabs: function() {
37369		var that = this,
37370			prevTabs = this.tabs,
37371			prevAnchors = this.anchors,
37372			prevPanels = this.panels;
37373
37374		this.tablist = this._getList().attr( "role", "tablist" );
37375		this._addClass( this.tablist, "ui-tabs-nav",
37376			"ui-helper-reset ui-helper-clearfix ui-widget-header" );
37377
37378		// Prevent users from focusing disabled tabs via click
37379		this.tablist
37380			.on( "mousedown" + this.eventNamespace, "> li", function( event ) {
37381				if ( $( this ).is( ".ui-state-disabled" ) ) {
37382					event.preventDefault();
37383				}
37384			} )
37385
37386			// Support: IE <9
37387			// Preventing the default action in mousedown doesn't prevent IE
37388			// from focusing the element, so if the anchor gets focused, blur.
37389			// We don't have to worry about focusing the previously focused
37390			// element since clicking on a non-focusable element should focus
37391			// the body anyway.
37392			.on( "focus" + this.eventNamespace, ".ui-tabs-anchor", function() {
37393				if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
37394					this.blur();
37395				}
37396			} );
37397
37398		this.tabs = this.tablist.find( "> li:has(a[href])" )
37399			.attr( {
37400				role: "tab",
37401				tabIndex: -1
37402			} );
37403		this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" );
37404
37405		this.anchors = this.tabs.map( function() {
37406			return $( "a", this )[ 0 ];
37407		} )
37408			.attr( {
37409				role: "presentation",
37410				tabIndex: -1
37411			} );
37412		this._addClass( this.anchors, "ui-tabs-anchor" );
37413
37414		this.panels = $();
37415
37416		this.anchors.each( function( i, anchor ) {
37417			var selector, panel, panelId,
37418				anchorId = $( anchor ).uniqueId().attr( "id" ),
37419				tab = $( anchor ).closest( "li" ),
37420				originalAriaControls = tab.attr( "aria-controls" );
37421
37422			// Inline tab
37423			if ( that._isLocal( anchor ) ) {
37424				selector = anchor.hash;
37425				panelId = selector.substring( 1 );
37426				panel = that.element.find( that._sanitizeSelector( selector ) );
37427
37428			// remote tab
37429			} else {
37430
37431				// If the tab doesn't already have aria-controls,
37432				// generate an id by using a throw-away element
37433				panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
37434				selector = "#" + panelId;
37435				panel = that.element.find( selector );
37436				if ( !panel.length ) {
37437					panel = that._createPanel( panelId );
37438					panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
37439				}
37440				panel.attr( "aria-live", "polite" );
37441			}
37442
37443			if ( panel.length ) {
37444				that.panels = that.panels.add( panel );
37445			}
37446			if ( originalAriaControls ) {
37447				tab.data( "ui-tabs-aria-controls", originalAriaControls );
37448			}
37449			tab.attr( {
37450				"aria-controls": panelId,
37451				"aria-labelledby": anchorId
37452			} );
37453			panel.attr( "aria-labelledby", anchorId );
37454		} );
37455
37456		this.panels.attr( "role", "tabpanel" );
37457		this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" );
37458
37459		// Avoid memory leaks (#10056)
37460		if ( prevTabs ) {
37461			this._off( prevTabs.not( this.tabs ) );
37462			this._off( prevAnchors.not( this.anchors ) );
37463			this._off( prevPanels.not( this.panels ) );
37464		}
37465	},
37466
37467	// Allow overriding how to find the list for rare usage scenarios (#7715)
37468	_getList: function() {
37469		return this.tablist || this.element.find( "ol, ul" ).eq( 0 );
37470	},
37471
37472	_createPanel: function( id ) {
37473		return $( "<div>" )
37474			.attr( "id", id )
37475			.data( "ui-tabs-destroy", true );
37476	},
37477
37478	_setOptionDisabled: function( disabled ) {
37479		var currentItem, li, i;
37480
37481		if ( $.isArray( disabled ) ) {
37482			if ( !disabled.length ) {
37483				disabled = false;
37484			} else if ( disabled.length === this.anchors.length ) {
37485				disabled = true;
37486			}
37487		}
37488
37489		// Disable tabs
37490		for ( i = 0; ( li = this.tabs[ i ] ); i++ ) {
37491			currentItem = $( li );
37492			if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
37493				currentItem.attr( "aria-disabled", "true" );
37494				this._addClass( currentItem, null, "ui-state-disabled" );
37495			} else {
37496				currentItem.removeAttr( "aria-disabled" );
37497				this._removeClass( currentItem, null, "ui-state-disabled" );
37498			}
37499		}
37500
37501		this.options.disabled = disabled;
37502
37503		this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null,
37504			disabled === true );
37505	},
37506
37507	_setupEvents: function( event ) {
37508		var events = {};
37509		if ( event ) {
37510			$.each( event.split( " " ), function( index, eventName ) {
37511				events[ eventName ] = "_eventHandler";
37512			} );
37513		}
37514
37515		this._off( this.anchors.add( this.tabs ).add( this.panels ) );
37516
37517		// Always prevent the default action, even when disabled
37518		this._on( true, this.anchors, {
37519			click: function( event ) {
37520				event.preventDefault();
37521			}
37522		} );
37523		this._on( this.anchors, events );
37524		this._on( this.tabs, { keydown: "_tabKeydown" } );
37525		this._on( this.panels, { keydown: "_panelKeydown" } );
37526
37527		this._focusable( this.tabs );
37528		this._hoverable( this.tabs );
37529	},
37530
37531	_setupHeightStyle: function( heightStyle ) {
37532		var maxHeight,
37533			parent = this.element.parent();
37534
37535		if ( heightStyle === "fill" ) {
37536			maxHeight = parent.height();
37537			maxHeight -= this.element.outerHeight() - this.element.height();
37538
37539			this.element.siblings( ":visible" ).each( function() {
37540				var elem = $( this ),
37541					position = elem.css( "position" );
37542
37543				if ( position === "absolute" || position === "fixed" ) {
37544					return;
37545				}
37546				maxHeight -= elem.outerHeight( true );
37547			} );
37548
37549			this.element.children().not( this.panels ).each( function() {
37550				maxHeight -= $( this ).outerHeight( true );
37551			} );
37552
37553			this.panels.each( function() {
37554				$( this ).height( Math.max( 0, maxHeight -
37555					$( this ).innerHeight() + $( this ).height() ) );
37556			} )
37557				.css( "overflow", "auto" );
37558		} else if ( heightStyle === "auto" ) {
37559			maxHeight = 0;
37560			this.panels.each( function() {
37561				maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
37562			} ).height( maxHeight );
37563		}
37564	},
37565
37566	_eventHandler: function( event ) {
37567		var options = this.options,
37568			active = this.active,
37569			anchor = $( event.currentTarget ),
37570			tab = anchor.closest( "li" ),
37571			clickedIsActive = tab[ 0 ] === active[ 0 ],
37572			collapsing = clickedIsActive && options.collapsible,
37573			toShow = collapsing ? $() : this._getPanelForTab( tab ),
37574			toHide = !active.length ? $() : this._getPanelForTab( active ),
37575			eventData = {
37576				oldTab: active,
37577				oldPanel: toHide,
37578				newTab: collapsing ? $() : tab,
37579				newPanel: toShow
37580			};
37581
37582		event.preventDefault();
37583
37584		if ( tab.hasClass( "ui-state-disabled" ) ||
37585
37586				// tab is already loading
37587				tab.hasClass( "ui-tabs-loading" ) ||
37588
37589				// can't switch durning an animation
37590				this.running ||
37591
37592				// click on active header, but not collapsible
37593				( clickedIsActive && !options.collapsible ) ||
37594
37595				// allow canceling activation
37596				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
37597			return;
37598		}
37599
37600		options.active = collapsing ? false : this.tabs.index( tab );
37601
37602		this.active = clickedIsActive ? $() : tab;
37603		if ( this.xhr ) {
37604			this.xhr.abort();
37605		}
37606
37607		if ( !toHide.length && !toShow.length ) {
37608			$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
37609		}
37610
37611		if ( toShow.length ) {
37612			this.load( this.tabs.index( tab ), event );
37613		}
37614		this._toggle( event, eventData );
37615	},
37616
37617	// Handles show/hide for selecting tabs
37618	_toggle: function( event, eventData ) {
37619		var that = this,
37620			toShow = eventData.newPanel,
37621			toHide = eventData.oldPanel;
37622
37623		this.running = true;
37624
37625		function complete() {
37626			that.running = false;
37627			that._trigger( "activate", event, eventData );
37628		}
37629
37630		function show() {
37631			that._addClass( eventData.newTab.closest( "li" ), "ui-tabs-active", "ui-state-active" );
37632
37633			if ( toShow.length && that.options.show ) {
37634				that._show( toShow, that.options.show, complete );
37635			} else {
37636				toShow.show();
37637				complete();
37638			}
37639		}
37640
37641		// Start out by hiding, then showing, then completing
37642		if ( toHide.length && this.options.hide ) {
37643			this._hide( toHide, this.options.hide, function() {
37644				that._removeClass( eventData.oldTab.closest( "li" ),
37645					"ui-tabs-active", "ui-state-active" );
37646				show();
37647			} );
37648		} else {
37649			this._removeClass( eventData.oldTab.closest( "li" ),
37650				"ui-tabs-active", "ui-state-active" );
37651			toHide.hide();
37652			show();
37653		}
37654
37655		toHide.attr( "aria-hidden", "true" );
37656		eventData.oldTab.attr( {
37657			"aria-selected": "false",
37658			"aria-expanded": "false"
37659		} );
37660
37661		// If we're switching tabs, remove the old tab from the tab order.
37662		// If we're opening from collapsed state, remove the previous tab from the tab order.
37663		// If we're collapsing, then keep the collapsing tab in the tab order.
37664		if ( toShow.length && toHide.length ) {
37665			eventData.oldTab.attr( "tabIndex", -1 );
37666		} else if ( toShow.length ) {
37667			this.tabs.filter( function() {
37668				return $( this ).attr( "tabIndex" ) === 0;
37669			} )
37670				.attr( "tabIndex", -1 );
37671		}
37672
37673		toShow.attr( "aria-hidden", "false" );
37674		eventData.newTab.attr( {
37675			"aria-selected": "true",
37676			"aria-expanded": "true",
37677			tabIndex: 0
37678		} );
37679	},
37680
37681	_activate: function( index ) {
37682		var anchor,
37683			active = this._findActive( index );
37684
37685		// Trying to activate the already active panel
37686		if ( active[ 0 ] === this.active[ 0 ] ) {
37687			return;
37688		}
37689
37690		// Trying to collapse, simulate a click on the current active header
37691		if ( !active.length ) {
37692			active = this.active;
37693		}
37694
37695		anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
37696		this._eventHandler( {
37697			target: anchor,
37698			currentTarget: anchor,
37699			preventDefault: $.noop
37700		} );
37701	},
37702
37703	_findActive: function( index ) {
37704		return index === false ? $() : this.tabs.eq( index );
37705	},
37706
37707	_getIndex: function( index ) {
37708
37709		// meta-function to give users option to provide a href string instead of a numerical index.
37710		if ( typeof index === "string" ) {
37711			index = this.anchors.index( this.anchors.filter( "[href$='" +
37712				$.ui.escapeSelector( index ) + "']" ) );
37713		}
37714
37715		return index;
37716	},
37717
37718	_destroy: function() {
37719		if ( this.xhr ) {
37720			this.xhr.abort();
37721		}
37722
37723		this.tablist
37724			.removeAttr( "role" )
37725			.off( this.eventNamespace );
37726
37727		this.anchors
37728			.removeAttr( "role tabIndex" )
37729			.removeUniqueId();
37730
37731		this.tabs.add( this.panels ).each( function() {
37732			if ( $.data( this, "ui-tabs-destroy" ) ) {
37733				$( this ).remove();
37734			} else {
37735				$( this ).removeAttr( "role tabIndex " +
37736					"aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded" );
37737			}
37738		} );
37739
37740		this.tabs.each( function() {
37741			var li = $( this ),
37742				prev = li.data( "ui-tabs-aria-controls" );
37743			if ( prev ) {
37744				li
37745					.attr( "aria-controls", prev )
37746					.removeData( "ui-tabs-aria-controls" );
37747			} else {
37748				li.removeAttr( "aria-controls" );
37749			}
37750		} );
37751
37752		this.panels.show();
37753
37754		if ( this.options.heightStyle !== "content" ) {
37755			this.panels.css( "height", "" );
37756		}
37757	},
37758
37759	enable: function( index ) {
37760		var disabled = this.options.disabled;
37761		if ( disabled === false ) {
37762			return;
37763		}
37764
37765		if ( index === undefined ) {
37766			disabled = false;
37767		} else {
37768			index = this._getIndex( index );
37769			if ( $.isArray( disabled ) ) {
37770				disabled = $.map( disabled, function( num ) {
37771					return num !== index ? num : null;
37772				} );
37773			} else {
37774				disabled = $.map( this.tabs, function( li, num ) {
37775					return num !== index ? num : null;
37776				} );
37777			}
37778		}
37779		this._setOptionDisabled( disabled );
37780	},
37781
37782	disable: function( index ) {
37783		var disabled = this.options.disabled;
37784		if ( disabled === true ) {
37785			return;
37786		}
37787
37788		if ( index === undefined ) {
37789			disabled = true;
37790		} else {
37791			index = this._getIndex( index );
37792			if ( $.inArray( index, disabled ) !== -1 ) {
37793				return;
37794			}
37795			if ( $.isArray( disabled ) ) {
37796				disabled = $.merge( [ index ], disabled ).sort();
37797			} else {
37798				disabled = [ index ];
37799			}
37800		}
37801		this._setOptionDisabled( disabled );
37802	},
37803
37804	load: function( index, event ) {
37805		index = this._getIndex( index );
37806		var that = this,
37807			tab = this.tabs.eq( index ),
37808			anchor = tab.find( ".ui-tabs-anchor" ),
37809			panel = this._getPanelForTab( tab ),
37810			eventData = {
37811				tab: tab,
37812				panel: panel
37813			},
37814			complete = function( jqXHR, status ) {
37815				if ( status === "abort" ) {
37816					that.panels.stop( false, true );
37817				}
37818
37819				that._removeClass( tab, "ui-tabs-loading" );
37820				panel.removeAttr( "aria-busy" );
37821
37822				if ( jqXHR === that.xhr ) {
37823					delete that.xhr;
37824				}
37825			};
37826
37827		// Not remote
37828		if ( this._isLocal( anchor[ 0 ] ) ) {
37829			return;
37830		}
37831
37832		this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
37833
37834		// Support: jQuery <1.8
37835		// jQuery <1.8 returns false if the request is canceled in beforeSend,
37836		// but as of 1.8, $.ajax() always returns a jqXHR object.
37837		if ( this.xhr && this.xhr.statusText !== "canceled" ) {
37838			this._addClass( tab, "ui-tabs-loading" );
37839			panel.attr( "aria-busy", "true" );
37840
37841			this.xhr
37842				.done( function( response, status, jqXHR ) {
37843
37844					// support: jQuery <1.8
37845					// http://bugs.jquery.com/ticket/11778
37846					setTimeout( function() {
37847						panel.html( response );
37848						that._trigger( "load", event, eventData );
37849
37850						complete( jqXHR, status );
37851					}, 1 );
37852				} )
37853				.fail( function( jqXHR, status ) {
37854
37855					// support: jQuery <1.8
37856					// http://bugs.jquery.com/ticket/11778
37857					setTimeout( function() {
37858						complete( jqXHR, status );
37859					}, 1 );
37860				} );
37861		}
37862	},
37863
37864	_ajaxSettings: function( anchor, event, eventData ) {
37865		var that = this;
37866		return {
37867
37868			// Support: IE <11 only
37869			// Strip any hash that exists to prevent errors with the Ajax request
37870			url: anchor.attr( "href" ).replace( /#.*$/, "" ),
37871			beforeSend: function( jqXHR, settings ) {
37872				return that._trigger( "beforeLoad", event,
37873					$.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
37874			}
37875		};
37876	},
37877
37878	_getPanelForTab: function( tab ) {
37879		var id = $( tab ).attr( "aria-controls" );
37880		return this.element.find( this._sanitizeSelector( "#" + id ) );
37881	}
37882} );
37883
37884// DEPRECATED
37885// TODO: Switch return back to widget declaration at top of file when this is removed
37886if ( $.uiBackCompat !== false ) {
37887
37888	// Backcompat for ui-tab class (now ui-tabs-tab)
37889	$.widget( "ui.tabs", $.ui.tabs, {
37890		_processTabs: function() {
37891			this._superApply( arguments );
37892			this._addClass( this.tabs, "ui-tab" );
37893		}
37894	} );
37895}
37896
37897return $.ui.tabs;
37898
37899} ) );
37900
37901
37902
37903
37904
37905
37906/*!
37907 * jQuery UI Tooltip 1.12.1
37908 * http://jqueryui.com
37909 *
37910 * Copyright jQuery Foundation and other contributors
37911 * Released under the MIT license.
37912 * http://jquery.org/license
37913 */
37914
37915//>>label: Tooltip
37916//>>group: Widgets
37917//>>description: Shows additional information for any element on hover or focus.
37918//>>docs: http://api.jqueryui.com/tooltip/
37919//>>demos: http://jqueryui.com/tooltip/
37920//>>css.structure: ../../themes/base/core.css
37921//>>css.structure: ../../themes/base/tooltip.css
37922//>>css.theme: ../../themes/base/theme.css
37923
37924( function( factory ) {
37925	if ( typeof define === "function" && define.amd ) {
37926
37927		// AMD. Register as an anonymous module.
37928		define( [
37929			"jquery",
37930			"../keycode",
37931			"../position",
37932			"../unique-id",
37933			"../version",
37934			"../widget"
37935		], factory );
37936	} else {
37937
37938		// Browser globals
37939		factory( jQuery );
37940	}
37941}( function( $ ) {
vendor: 13,483 bytes, lines 37941-38422
37941
37942
37943$.widget( "ui.tooltip", {
37944	version: "1.12.1",
37945	options: {
37946		classes: {
37947			"ui-tooltip": "ui-corner-all ui-widget-shadow"
37948		},
37949		content: function() {
37950
37951			// support: IE<9, Opera in jQuery <1.7
37952			// .text() can't accept undefined, so coerce to a string
37953			var title = $( this ).attr( "title" ) || "";
37954
37955			// Escape title, since we're going from an attribute to raw HTML
37956			return $( "<a>" ).text( title ).html();
37957		},
37958		hide: true,
37959
37960		// Disabled elements have inconsistent behavior across browsers (#8661)
37961		items: "[title]:not([disabled])",
37962		position: {
37963			my: "left top+15",
37964			at: "left bottom",
37965			collision: "flipfit flip"
37966		},
37967		show: true,
37968		track: false,
37969
37970		// Callbacks
37971		close: null,
37972		open: null
37973	},
37974
37975	_addDescribedBy: function( elem, id ) {
37976		var describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ );
37977		describedby.push( id );
37978		elem
37979			.data( "ui-tooltip-id", id )
37980			.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
37981	},
37982
37983	_removeDescribedBy: function( elem ) {
37984		var id = elem.data( "ui-tooltip-id" ),
37985			describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ),
37986			index = $.inArray( id, describedby );
37987
37988		if ( index !== -1 ) {
37989			describedby.splice( index, 1 );
37990		}
37991
37992		elem.removeData( "ui-tooltip-id" );
37993		describedby = $.trim( describedby.join( " " ) );
37994		if ( describedby ) {
37995			elem.attr( "aria-describedby", describedby );
37996		} else {
37997			elem.removeAttr( "aria-describedby" );
37998		}
37999	},
38000
38001	_create: function() {
38002		this._on( {
38003			mouseover: "open",
38004			focusin: "open"
38005		} );
38006
38007		// IDs of generated tooltips, needed for destroy
38008		this.tooltips = {};
38009
38010		// IDs of parent tooltips where we removed the title attribute
38011		this.parents = {};
38012
38013		// Append the aria-live region so tooltips announce correctly
38014		this.liveRegion = $( "<div>" )
38015			.attr( {
38016				role: "log",
38017				"aria-live": "assertive",
38018				"aria-relevant": "additions"
38019			} )
38020			.appendTo( this.document[ 0 ].body );
38021		this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
38022
38023		this.disabledTitles = $( [] );
38024	},
38025
38026	_setOption: function( key, value ) {
38027		var that = this;
38028
38029		this._super( key, value );
38030
38031		if ( key === "content" ) {
38032			$.each( this.tooltips, function( id, tooltipData ) {
38033				that._updateContent( tooltipData.element );
38034			} );
38035		}
38036	},
38037
38038	_setOptionDisabled: function( value ) {
38039		this[ value ? "_disable" : "_enable" ]();
38040	},
38041
38042	_disable: function() {
38043		var that = this;
38044
38045		// Close open tooltips
38046		$.each( this.tooltips, function( id, tooltipData ) {
38047			var event = $.Event( "blur" );
38048			event.target = event.currentTarget = tooltipData.element[ 0 ];
38049			that.close( event, true );
38050		} );
38051
38052		// Remove title attributes to prevent native tooltips
38053		this.disabledTitles = this.disabledTitles.add(
38054			this.element.find( this.options.items ).addBack()
38055				.filter( function() {
38056					var element = $( this );
38057					if ( element.is( "[title]" ) ) {
38058						return element
38059							.data( "ui-tooltip-title", element.attr( "title" ) )
38060							.removeAttr( "title" );
38061					}
38062				} )
38063		);
38064	},
38065
38066	_enable: function() {
38067
38068		// restore title attributes
38069		this.disabledTitles.each( function() {
38070			var element = $( this );
38071			if ( element.data( "ui-tooltip-title" ) ) {
38072				element.attr( "title", element.data( "ui-tooltip-title" ) );
38073			}
38074		} );
38075		this.disabledTitles = $( [] );
38076	},
38077
38078	open: function( event ) {
38079		var that = this,
38080			target = $( event ? event.target : this.element )
38081
38082				// we need closest here due to mouseover bubbling,
38083				// but always pointing at the same event target
38084				.closest( this.options.items );
38085
38086		// No element to show a tooltip for or the tooltip is already open
38087		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
38088			return;
38089		}
38090
38091		if ( target.attr( "title" ) ) {
38092			target.data( "ui-tooltip-title", target.attr( "title" ) );
38093		}
38094
38095		target.data( "ui-tooltip-open", true );
38096
38097		// Kill parent tooltips, custom or native, for hover
38098		if ( event && event.type === "mouseover" ) {
38099			target.parents().each( function() {
38100				var parent = $( this ),
38101					blurEvent;
38102				if ( parent.data( "ui-tooltip-open" ) ) {
38103					blurEvent = $.Event( "blur" );
38104					blurEvent.target = blurEvent.currentTarget = this;
38105					that.close( blurEvent, true );
38106				}
38107				if ( parent.attr( "title" ) ) {
38108					parent.uniqueId();
38109					that.parents[ this.id ] = {
38110						element: this,
38111						title: parent.attr( "title" )
38112					};
38113					parent.attr( "title", "" );
38114				}
38115			} );
38116		}
38117
38118		this._registerCloseHandlers( event, target );
38119		this._updateContent( target, event );
38120	},
38121
38122	_updateContent: function( target, event ) {
38123		var content,
38124			contentOption = this.options.content,
38125			that = this,
38126			eventType = event ? event.type : null;
38127
38128		if ( typeof contentOption === "string" || contentOption.nodeType ||
38129				contentOption.jquery ) {
38130			return this._open( event, target, contentOption );
38131		}
38132
38133		content = contentOption.call( target[ 0 ], function( response ) {
38134
38135			// IE may instantly serve a cached response for ajax requests
38136			// delay this call to _open so the other call to _open runs first
38137			that._delay( function() {
38138
38139				// Ignore async response if tooltip was closed already
38140				if ( !target.data( "ui-tooltip-open" ) ) {
38141					return;
38142				}
38143
38144				// JQuery creates a special event for focusin when it doesn't
38145				// exist natively. To improve performance, the native event
38146				// object is reused and the type is changed. Therefore, we can't
38147				// rely on the type being correct after the event finished
38148				// bubbling, so we set it back to the previous value. (#8740)
38149				if ( event ) {
38150					event.type = eventType;
38151				}
38152				this._open( event, target, response );
38153			} );
38154		} );
38155		if ( content ) {
38156			this._open( event, target, content );
38157		}
38158	},
38159
38160	_open: function( event, target, content ) {
38161		var tooltipData, tooltip, delayedShow, a11yContent,
38162			positionOption = $.extend( {}, this.options.position );
38163
38164		if ( !content ) {
38165			return;
38166		}
38167
38168		// Content can be updated multiple times. If the tooltip already
38169		// exists, then just update the content and bail.
38170		tooltipData = this._find( target );
38171		if ( tooltipData ) {
38172			tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content );
38173			return;
38174		}
38175
38176		// If we have a title, clear it to prevent the native tooltip
38177		// we have to check first to avoid defining a title if none exists
38178		// (we don't want to cause an element to start matching [title])
38179		//
38180		// We use removeAttr only for key events, to allow IE to export the correct
38181		// accessible attributes. For mouse events, set to empty string to avoid
38182		// native tooltip showing up (happens only when removing inside mouseover).
38183		if ( target.is( "[title]" ) ) {
38184			if ( event && event.type === "mouseover" ) {
38185				target.attr( "title", "" );
38186			} else {
38187				target.removeAttr( "title" );
38188			}
38189		}
38190
38191		tooltipData = this._tooltip( target );
38192		tooltip = tooltipData.tooltip;
38193		this._addDescribedBy( target, tooltip.attr( "id" ) );
38194		tooltip.find( ".ui-tooltip-content" ).html( content );
38195
38196		// Support: Voiceover on OS X, JAWS on IE <= 9
38197		// JAWS announces deletions even when aria-relevant="additions"
38198		// Voiceover will sometimes re-read the entire log region's contents from the beginning
38199		this.liveRegion.children().hide();
38200		a11yContent = $( "<div>" ).html( tooltip.find( ".ui-tooltip-content" ).html() );
38201		a11yContent.removeAttr( "name" ).find( "[name]" ).removeAttr( "name" );
38202		a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" );
38203		a11yContent.appendTo( this.liveRegion );
38204
38205		function position( event ) {
38206			positionOption.of = event;
38207			if ( tooltip.is( ":hidden" ) ) {
38208				return;
38209			}
38210			tooltip.position( positionOption );
38211		}
38212		if ( this.options.track && event && /^mouse/.test( event.type ) ) {
38213			this._on( this.document, {
38214				mousemove: position
38215			} );
38216
38217			// trigger once to override element-relative positioning
38218			position( event );
38219		} else {
38220			tooltip.position( $.extend( {
38221				of: target
38222			}, this.options.position ) );
38223		}
38224
38225		tooltip.hide();
38226
38227		this._show( tooltip, this.options.show );
38228
38229		// Handle tracking tooltips that are shown with a delay (#8644). As soon
38230		// as the tooltip is visible, position the tooltip using the most recent
38231		// event.
38232		// Adds the check to add the timers only when both delay and track options are set (#14682)
38233		if ( this.options.track && this.options.show && this.options.show.delay ) {
38234			delayedShow = this.delayedShow = setInterval( function() {
38235				if ( tooltip.is( ":visible" ) ) {
38236					position( positionOption.of );
38237					clearInterval( delayedShow );
38238				}
38239			}, $.fx.interval );
38240		}
38241
38242		this._trigger( "open", event, { tooltip: tooltip } );
38243	},
38244
38245	_registerCloseHandlers: function( event, target ) {
38246		var events = {
38247			keyup: function( event ) {
38248				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
38249					var fakeEvent = $.Event( event );
38250					fakeEvent.currentTarget = target[ 0 ];
38251					this.close( fakeEvent, true );
38252				}
38253			}
38254		};
38255
38256		// Only bind remove handler for delegated targets. Non-delegated
38257		// tooltips will handle this in destroy.
38258		if ( target[ 0 ] !== this.element[ 0 ] ) {
38259			events.remove = function() {
38260				this._removeTooltip( this._find( target ).tooltip );
38261			};
38262		}
38263
38264		if ( !event || event.type === "mouseover" ) {
38265			events.mouseleave = "close";
38266		}
38267		if ( !event || event.type === "focusin" ) {
38268			events.focusout = "close";
38269		}
38270		this._on( true, target, events );
38271	},
38272
38273	close: function( event ) {
38274		var tooltip,
38275			that = this,
38276			target = $( event ? event.currentTarget : this.element ),
38277			tooltipData = this._find( target );
38278
38279		// The tooltip may already be closed
38280		if ( !tooltipData ) {
38281
38282			// We set ui-tooltip-open immediately upon open (in open()), but only set the
38283			// additional data once there's actually content to show (in _open()). So even if the
38284			// tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in
38285			// the period between open() and _open().
38286			target.removeData( "ui-tooltip-open" );
38287			return;
38288		}
38289
38290		tooltip = tooltipData.tooltip;
38291
38292		// Disabling closes the tooltip, so we need to track when we're closing
38293		// to avoid an infinite loop in case the tooltip becomes disabled on close
38294		if ( tooltipData.closing ) {
38295			return;
38296		}
38297
38298		// Clear the interval for delayed tracking tooltips
38299		clearInterval( this.delayedShow );
38300
38301		// Only set title if we had one before (see comment in _open())
38302		// If the title attribute has changed since open(), don't restore
38303		if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) {
38304			target.attr( "title", target.data( "ui-tooltip-title" ) );
38305		}
38306
38307		this._removeDescribedBy( target );
38308
38309		tooltipData.hiding = true;
38310		tooltip.stop( true );
38311		this._hide( tooltip, this.options.hide, function() {
38312			that._removeTooltip( $( this ) );
38313		} );
38314
38315		target.removeData( "ui-tooltip-open" );
38316		this._off( target, "mouseleave focusout keyup" );
38317
38318		// Remove 'remove' binding only on delegated targets
38319		if ( target[ 0 ] !== this.element[ 0 ] ) {
38320			this._off( target, "remove" );
38321		}
38322		this._off( this.document, "mousemove" );
38323
38324		if ( event && event.type === "mouseleave" ) {
38325			$.each( this.parents, function( id, parent ) {
38326				$( parent.element ).attr( "title", parent.title );
38327				delete that.parents[ id ];
38328			} );
38329		}
38330
38331		tooltipData.closing = true;
38332		this._trigger( "close", event, { tooltip: tooltip } );
38333		if ( !tooltipData.hiding ) {
38334			tooltipData.closing = false;
38335		}
38336	},
38337
38338	_tooltip: function( element ) {
38339		var tooltip = $( "<div>" ).attr( "role", "tooltip" ),
38340			content = $( "<div>" ).appendTo( tooltip ),
38341			id = tooltip.uniqueId().attr( "id" );
38342
38343		this._addClass( content, "ui-tooltip-content" );
38344		this._addClass( tooltip, "ui-tooltip", "ui-widget ui-widget-content" );
38345
38346		tooltip.appendTo( this._appendTo( element ) );
38347
38348		return this.tooltips[ id ] = {
38349			element: element,
38350			tooltip: tooltip
38351		};
38352	},
38353
38354	_find: function( target ) {
38355		var id = target.data( "ui-tooltip-id" );
38356		return id ? this.tooltips[ id ] : null;
38357	},
38358
38359	_removeTooltip: function( tooltip ) {
38360		tooltip.remove();
38361		delete this.tooltips[ tooltip.attr( "id" ) ];
38362	},
38363
38364	_appendTo: function( target ) {
38365		var element = target.closest( ".ui-front, dialog" );
38366
38367		if ( !element.length ) {
38368			element = this.document[ 0 ].body;
38369		}
38370
38371		return element;
38372	},
38373
38374	_destroy: function() {
38375		var that = this;
38376
38377		// Close open tooltips
38378		$.each( this.tooltips, function( id, tooltipData ) {
38379
38380			// Delegate to close method to handle common cleanup
38381			var event = $.Event( "blur" ),
38382				element = tooltipData.element;
38383			event.target = event.currentTarget = element[ 0 ];
38384			that.close( event, true );
38385
38386			// Remove immediately; destroying an open tooltip doesn't use the
38387			// hide animation
38388			$( "#" + id ).remove();
38389
38390			// Restore the title
38391			if ( element.data( "ui-tooltip-title" ) ) {
38392
38393				// If the title attribute has changed since open(), don't restore
38394				if ( !element.attr( "title" ) ) {
38395					element.attr( "title", element.data( "ui-tooltip-title" ) );
38396				}
38397				element.removeData( "ui-tooltip-title" );
38398			}
38399		} );
38400		this.liveRegion.remove();
38401	}
38402} );
38403
38404// DEPRECATED
38405// TODO: Switch return back to widget declaration at top of file when this is removed
38406if ( $.uiBackCompat !== false ) {
38407
38408	// Backcompat for tooltipClass option
38409	$.widget( "ui.tooltip", $.ui.tooltip, {
38410		options: {
38411			tooltipClass: null
38412		},
38413		_tooltip: function() {
38414			var tooltipData = this._superApply( arguments );
38415			if ( this.options.tooltipClass ) {
38416				tooltipData.tooltip.addClass( this.options.tooltipClass );
38417			}
38418			return tooltipData;
38419		}
38420	} );
38421}
38422
38423return $.ui.tooltip;
38424
38425} ) );
38426
38427
38428
38429
38430
38431
38432
38433
38434
38435
38436
38437
38438
38439
38440
38441
38442
38443
38444
38445
38446
38447
38448
38449
38450
38451
38452
38453
38454
38455
38456
38457
38458
38459
38460
38461
38462
38463
38464
38465
38466
38467
38468
38469
38470
38471
38472
38473
38474
38475
38476
38477
38478
38479
38480
38481
38482/*
38483 * DropKick 1.4
38484 *
38485 * Highly customizable <select> lists
38486 * https://github.com/robdel12/DropKick
38487 *
38488 * Created by: Jamie Lottering <https://github.com/JamieLottering> <https://twitter.com/JamieLottering>
38489 *
38490 *
38491*/
38492(function(e,t,n){"use strict";var r=navigator.userAgent.match(/MSIE ([0-9]{1,}[\.0-9]{0,})/),i=!!r,s=i&&parseFloat(r[1])<7,o=navigator.userAgent.match(/iPad|iPhone|Android|IEMobile|BlackBerry/i),u={},a=[],f={left:37,up:38,right:39,down:40,enter:13,tab:9,zero:48,z:90,last:221},l=['<div class="dk_container" id="dk_container_{{ id }}" tabindex="{{ tabindex }}" aria-hidden="true">','<a class="dk_toggle dk_label">{{ label }}</a>','<div class="dk_options">','<ul class="dk_options_inner" role="main" aria-hidden="true">',"</ul>","</div>","</div>"].join(""),c='<li><a data-dk-dropdown-value="{{ value }}">{{ text }}</a></li>',h={startSpeed:400,theme:!1,changes:!1,syncReverse:!0,nativeMobile:!0,autoWidth:!0},p=null,d=null,v=function(e,t,n){var r,i,s,o;r=e.attr("data-dk-dropdown-value");i=e.text();s=t.data("dropkick");o=s.$select;o.val(r).trigger("change");t.find(".dk_label").text(i);n=n||!1;s.settings.change&&!n&&!s.settings.syncReverse&&s.settings.change.call(o,r,i)},m=function(e){e.removeClass("dk_open dk_open_top");p=null},g=function(n){var r=n.find(".dk_toggle"),i=n.find(".dk_options").outerHeight(),s=e(t).height()-r.outerHeight()-r.offset().top+e(t).scrollTop(),o=r.offset().top-e(t).scrollTop();return i<o?i<s:!0},y=function(e,t,n){var r=e.find(".dk_options_inner"),i=t.prevAll("li").outerHeight()*t.prevAll("li").length,s=r.scrollTop(),o=r.height()+r.scrollTop()-t.outerHeight();(n&&n.type==="keydown"||i<s||i>o)&&r.scrollTop(i)},b=function(e,t){var n=g(e);p=e.toggleClass("dk_open");var n=g(e),r=n?"dk_open":"dk_open_top dk_open";e.find(".dk_options").css({top:n?e.find(".dk_toggle").outerHeight()-1:"",bottom:n?"":e.find(".dk_toggle").outerHeight()-1});p=e.addClass(r);y(e,e.find(".dk_option_current"),t)},w=function(e,t,n){t.find(".dk_option_current").removeClass("dk_option_current");e.addClass("dk_option_current");y(t,e,n)},E=function(t,n){var r=t.keyCode,i=n.data("dropkick"),s=String.fromCharCode(r),o=n.find(".dk_options"),u=n.hasClass("dk_open"),a=o.find("li"),l=n.find(".dk_option_current"),c=a.first(),h=a.last(),p,d,g,y,E,S,x;switch(r){case f.enter:if(u){if(!l.hasClass("disabled")){v(l.find("a"),n);m(n)}}else b(n,t);t.preventDefault();break;case f.tab:if(u){l.length&&v(l.find("a"),n);m(n)}break;case f.up:d=l.prev("li");u?d.length?w(d,n,t):w(h,n,t):b(n,t);t.preventDefault();break;case f.down:if(u){p=l.next("li").first();p.length?w(p,n,t):w(c,n,t)}else b(n,t);t.preventDefault();break;default:}if(r>=f.zero&&r<=f.z){g=(new Date).getTime();if(i.finder===null||i.finder===undefined){i.finder=s.toUpperCase();i.timer=g}else if(g>parseInt(i.timer,10)+1e3){i.finder=s.toUpperCase();i.timer=g}else{i.finder=i.finder+s.toUpperCase();i.timer=g}y=a.find("a");for(E=0,S=y.length;E<S;E++){x=e(y[E]);if(x.html().toUpperCase().indexOf(i.finder)===0){v(x,n);w(x.parent(),n,t);break}}n.data("dropkick",i)}},S=function(t){return e.trim(t).length>0?t:!1},x=function(t,n){var r=t.replace("{{ id }}",n.id).replace("{{ label }}",n.label).replace("{{ tabindex }}",n.tabindex),i=[],s,o,u,a,f;if(n.options&&n.options.length)for(o=0,u=n.options.length;o<u;o++){a=e(n.options[o]);o===0&&a.attr("selected")!==undefined&&a.attr("disabled")!==undefined?f=null:f=c.replace("{{ value }}",a.val()).replace("{{ current }}",S(a.val())===n.value?"dk_option_current":"").replace("{{ disabled }}",a.attr("disabled")!==undefined?"disabled":"").replace("{{ text }}",e.trim(a.html()));i[i.length]=f}s=e(r);s.find(".dk_options_inner").html(i.join(""));return s};s||(n.documentElement.className=n.documentElement.className+" dk_fouc");u.init=function(t){t=e.extend({},h,t);l=t.dropdownTemplate?t.dropdownTemplate:l;c=t.optionTemplate?t.optionTemplate:c;return this.each(function(){var n=e(this),r=n.find(":selected").first(),i=n.find("option"),s=n.data("dropkick")||{},u=n.attr("id")||n.attr("name"),f=t.width||n.outerWidth(),c=n.attr("tabindex")||"0",h=!1,p,v;if(s.id)return n;s.settings=t;s.tabindex=c;s.id=u;s.$original=r;s.$select=n;s.value=S(n.val())||S(r.attr("value"));s.label=r.text();s.options=i;h=x(l,s);
38492s.settings.autoWidth&&h.find(".dk_toggle").css({width:f+"px"});n.before(h).appendTo(h);h=e('div[id="dk_container_'+u+'"]').fadeIn(t.startSpeed);p=t.theme||"default";h.addClass("dk_theme_"+p);s.theme=p;s.$dk=h;n.data("dropkick",s);h.addClass(n.attr("class"));h.data("dropkick",s);a[a.length]=n;h.on("focus.dropkick",function(){d=h.addClass("dk_focus")}).on("blur.dropkick",function(){h.removeClass("dk_focus");d=null});o&&s.settings.nativeMobile&&h.addClass("dk_mobile");s.settings.syncReverse&&n.on("change",function(t){var r=n.val(),i=e('a[data-dk-dropdown-value="'+r+'"]',h),o=i.text();h.find(".dk_label").text(o);s.settings.change&&s.settings.change.call(n,r,o);w(i.parent(),h,t)});v=n.attr("form")?e("#"+n.attr("form").replace(" ",", #")):n.closest("form");v.length&&v.on("reset",function(){n.dropkick("reset")})})};u.theme=function(t){var n=e(this).data("dropkick"),r=n.$dk,i="dk_theme_"+n.theme;r.removeClass(i).addClass("dk_theme_"+t);n.theme=t};u.reset=function(){return this.each(function(){var t=e(this).data("dropkick"),n=t.$dk,r=e('a[data-dk-dropdown-value="'+t.$original.attr("value")+'"]',n);t.$original.prop("selected",!0);n.find(".dk_label").text(t.label);w(r.parent(),n)})};u.setValue=function(t){return this.each(function(){var n=e(this).data("dropkick").$dk,r=e('.dk_options a[data-dk-dropdown-value="'+t+'"]',n);r.length?v(r,n)|w(r.parent(),n):console.warn("There is no option with this value in "+n.selector)})};u.refresh=function(){return this.each(function(){var t=e(this).data("dropkick"),n=t.$select,r=t.$dk,i,s;t.options=n.find("option");s=x(l,t).find(".dk_options_inner");r.find(".dk_options_inner").replaceWith(s);i=e('a[data-dk-dropdown-value="'+n.val()+'"]',r);w(i.parent(),r)})};e.fn.dropkick=function(e){if(!s){if(u[e])return u[e].apply(this,Array.prototype.slice.call(arguments,1));if(typeof e=="object"||!e)return u.init.apply(this,arguments)}};e(function(){e(n).on(i?"mousedown":"click",".dk_options a",function(){var t=e(this),n=t.parents(".dk_container").first();if(!t.parent().hasClass("disabled")){v(t,n);w(t.parent(),n);m(n)}return!1});e(n).on("keydown.dk_nav",function(e){var t;p?t=p:d&&(t=d);t&&E(e,t)});e(n).on("click",null,function(t){var n=e(t.target),r;if(p&&n.closest(".dk_container").length===0)m(p);else{if(n.is(".dk_toggle, .dk_label")){r=n.parents(".dk_container").first();if(r.hasClass("dk_open"))m(r);else{p&&m(p);b(r,t)}return!1}n.attr("for")&&!!e("#dk_container_"+n.attr("for"))[0]&&e("#dk_container_"+n.attr("for")).trigger("focus.dropkick")}});var r="onwheel"in t?"wheel":"onmousewheel"in n?"mousewheel":"MouseScrollEvent"in t?"DOMMouseScroll MozMousePixelScroll":!1;r&&e(n).on(r,".dk_options_inner",function(e){var t=e.originalEvent.wheelDelta||-e.originalEvent.deltaY||-e.originalEvent.detail;if(i){this.scrollTop-=Math.round(t/10);return!1}return t>0&&this.scrollTop<=0||t<0&&this.scrollTop>=this.scrollHeight-this.offsetHeight?!1:!0})})})(jQuery,window,document);
38493/*!
38494 * Client Side Validations JS - v0.4.0 (https://github.com/DavyJonesLocker/client_side_validations)
38495 * Copyright (c) 2023 Geremia Taglialatela, Brian Cardarella
38496 * Licensed under MIT (https://opensource.org/licenses/mit-license.php)
38497 */
38498
38499
38500(function (global, factory) {
38501  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) :
38502  typeof define === 'function' && define.amd ? define(['jquery'], factory) :
38503  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ClientSideValidations = factory(global.jQuery));
38504})(this, (function (jQuery) { 'use strict';
38505
38506  function _typeof(o) {
38507    "@babel/helpers - typeof";
38508
38509    return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
38510      return typeof o;
38511    } : function (o) {
38512      return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
38513    }, _typeof(o);
38514  }
38515
38516  var arrayHasValue = function arrayHasValue(value, otherValues) {
38517    for (var i = 0, l = otherValues.length; i < l; i++) {
38518      if (value === otherValues[i]) {
38519        return true;
38520      }
38521    }
38522    return false;
38523  };
38524  var createElementFromHTML = function createElementFromHTML(html) {
38525    var element = document.createElement('div');
38526    element.innerHTML = html;
38527    return element.firstChild;
38528  };
38529  var isValuePresent = function isValuePresent(value) {
38530    return !/^\s*$/.test(value || '');
38531  };
38532
38533  var ClientSideValidations = {
38534    callbacks: {
38535      element: {
38536        after: function after($element, eventData) {},
38537        before: function before($element, eventData) {},
38538        fail: function fail($element, message, addError, eventData) {
38539          return addError();
38540        },
38541        pass: function pass($element, removeError, eventData) {
38542          return removeError();
38543        }
38544      },
38545      form: {
38546        after: function after($form, eventData) {},
38547        before: function before($form, eventData) {},
38548        fail: function fail($form, eventData) {},
38549        pass: function pass($form, eventData) {}
38550      }
38551    },
38552    eventsToBind: {
38553      form: function form(_form, $form) {
38554        return {
38555          'submit.ClientSideValidations': function submitClientSideValidations(eventData) {
38556            if (!$form.isValid(_form.ClientSideValidations.settings.validators)) {
38557              eventData.preventDefault();
38558              eventData.stopImmediatePropagation();
38559            }
38560          },
38561          'ajax:beforeSend.ClientSideValidations': function ajaxBeforeSendClientSideValidations(eventData) {
38562            if (eventData.target === this) {
38563              $form.isValid(_form.ClientSideValidations.settings.validators);
38564            }
38565          },
38566          'form:validate:after.ClientSideValidations': function formValidateAfterClientSideValidations(eventData) {
38567            ClientSideValidations.callbacks.form.after($form, eventData);
38568          },
38569          'form:validate:before.ClientSideValidations': function formValidateBeforeClientSideValidations(eventData) {
38570            ClientSideValidations.callbacks.form.before($form, eventData);
38571          },
38572          'form:validate:fail.ClientSideValidations': function formValidateFailClientSideValidations(eventData) {
38573            ClientSideValidations.callbacks.form.fail($form, eventData);
38574          },
38575          'form:validate:pass.ClientSideValidations': function formValidatePassClientSideValidations(eventData) {
38576            ClientSideValidations.callbacks.form.pass($form, eventData);
38577          }
38578        };
38579      },
38580      input: function input(form) {
38581        return {
38582          'focusout.ClientSideValidations': function focusoutClientSideValidations() {
38583            jQuery(this).isValid(form.ClientSideValidations.settings.validators);
38584          },
38585          'change.ClientSideValidations': function changeClientSideValidations() {
38586            jQuery(this).data('changed', true);
38587          },
38588          'element:validate:after.ClientSideValidations': function elementValidateAfterClientSideValidations(eventData) {
38589            ClientSideValidations.callbacks.element.after(jQuery(this), eventData);
38590          },
38591          'element:validate:before.ClientSideValidations': function elementValidateBeforeClientSideValidations(eventData) {
38592            ClientSideValidations.callbacks.element.before(jQuery(this), eventData);
38593          },
38594          'element:validate:fail.ClientSideValidations': function elementValidateFailClientSideValidations(eventData, message) {
38595            var $element = jQuery(this);
38596            ClientSideValidations.callbacks.element.fail($element, message, function () {
38597              form.ClientSideValidations.addError($element, message);
38598            }, eventData);
38599          },
38600          'element:validate:pass.ClientSideValidations': function elementValidatePassClientSideValidations(eventData) {
38601            var $element = jQuery(this);
38602            ClientSideValidations.callbacks.element.pass($element, function () {
38603              form.ClientSideValidations.removeError($element);
38604            }, eventData);
38605          }
38606        };
38607      },
38608      inputConfirmation: function inputConfirmation($element, form) {
38609        return {
38610          'focusout.ClientSideValidations': function focusoutClientSideValidations() {
38611            $element.data('changed', true).isValid(form.ClientSideValidations.settings.val
38611idators);
38612          },
38613          'keyup.ClientSideValidations': function keyupClientSideValidations() {
38614            $element.data('changed', true).isValid(form.ClientSideValidations.settings.validators);
38615          }
38616        };
38617      }
38618    },
38619    enablers: {
38620      form: function form(_form2) {
38621        var $form = jQuery(_form2);
38622        _form2.ClientSideValidations = {
38623          settings: $form.data('clientSideValidations'),
38624          addError: function addError($element, message) {
38625            return ClientSideValidations.formBuilders[_form2.ClientSideValidations.settings.html_settings.type].add($element, _form2.ClientSideValidations.settings.html_settings, message);
38626          },
38627          removeError: function removeError($element) {
38628            return ClientSideValidations.formBuilders[_form2.ClientSideValidations.settings.html_settings.type].remove($element, _form2.ClientSideValidations.settings.html_settings);
38629          }
38630        };
38631        var eventsToBind = ClientSideValidations.eventsToBind.form(_form2, $form);
38632        for (var eventName in eventsToBind) {
38633          var eventFunction = eventsToBind[eventName];
38634          $form.on(eventName, eventFunction);
38635        }
38636        $form.find(ClientSideValidations.selectors.inputs).each(function () {
38637          ClientSideValidations.enablers.input(this);
38638        });
38639      },
38640      input: function input(_input) {
38641        var $input = jQuery(_input);
38642        var form = _input.form;
38643        var $form = jQuery(form);
38644        var eventsToBind = ClientSideValidations.eventsToBind.input(form);
38645        for (var eventName in eventsToBind) {
38646          var eventFunction = eventsToBind[eventName];
38647          $input.filter(':not(:radio):not([id$=_confirmation])').each(function () {
38648            jQuery(this).attr('data-validate', true);
38649          }).on(eventName, eventFunction);
38650        }
38651        $input.filter(':checkbox').on('change.ClientSideValidations', function () {
38652          jQuery(this).isValid(form.ClientSideValidations.settings.validators);
38653        });
38654        $input.filter('[id$=_confirmation]').each(function () {
38655          var $element = jQuery(this);
38656          var $elementToConfirm = $form.find("#".concat(this.id.match(/(.+)_confirmation/)[1], ":input"));
38657          if ($elementToConfirm.length) {
38658            var _eventsToBind = ClientSideValidations.eventsToBind.inputConfirmation($elementToConfirm, form);
38659            for (var _eventName in _eventsToBind) {
38660              var _eventFunction = _eventsToBind[_eventName];
38661              jQuery("#".concat($element.attr('id'))).on(_eventName, _eventFunction);
38662            }
38663          }
38664        });
38665      }
38666    },
38667    formBuilders: {
38668      'ActionView::Helpers::FormBuilder': {
38669        add: function add($element, settings, message) {
38670          var element = $element[0];
38671          var form = element.form;
38672          var inputErrorTemplate = createElementFromHTML(settings.input_tag);
38673          var inputErrorElement = element.closest(".".concat(inputErrorTemplate.getAttribute('class').replace(/ /g, '.')));
38674          if (!inputErrorElement) {
38675            inputErrorElement = inputErrorTemplate;
38676            if (element.getAttribute('autofocus')) {
38677              element.setAttribute('autofocus', false);
38678            }
38679            element.before(inputErrorElement);
38680            inputErrorElement.querySelector('span#input_tag').replaceWith(element);
38681            var inputErrorLabelMessageElement = inputErrorElement.querySelector('label.message');
38682            if (inputErrorLabelMessageElement) {
38683              inputErrorLabelMessageElement.setAttribute('for', element.id);
38684            }
38685          }
38686          var labelElement = form.querySelector("label[for=\"".concat(element.id, "\"]:not(.message)"));
38687          if (labelElement) {
38688            var labelErrorTemplate = createElementFromHTML(settings.label_tag);
38689            var labelErrorContainer = labelElement.closest(".".concat(labelErrorTemplate.getAttribute('class').replace(/ /g, '.')));
38690            if (!labelErrorContainer) {
38691              labelElement.after(labelErrorTemplate);
38692              labelErrorTemplate.querySelector('label#label_tag').replaceWith(labelElement);
38693            }
38694          }
38695          var labelMessageElement = form.querySelector("label.message[for=\"".concat(element.i
38695d, "\"]"));
38696          if (labelMessageElement) {
38697            labelMessageElement.textContent = message;
38698          }
38699        },
38700        remove: function remove($element, settings) {
38701          var element = $element[0];
38702          var form = element.form;
38703          var inputErrorClass = createElementFromHTML(settings.input_tag).getAttribute('class');
38704          var inputErrorElement = element.closest(".".concat(inputErrorClass.replace(/ /g, '.')));
38705          if (inputErrorElement) {
38706            inputErrorElement.querySelector("#".concat(element.id)).remove();
38707            inputErrorElement.replaceWith(element);
38708          }
38709          var labelElement = form.querySelector("label[for=\"".concat(element.id, "\"]:not(.message)"));
38710          if (labelElement) {
38711            var labelErrorClass = createElementFromHTML(settings.label_tag).getAttribute('class');
38712            var labelErrorElement = labelElement.closest(".".concat(labelErrorClass.replace(/ /g, '.')));
38713            if (labelErrorElement) {
38714              labelErrorElement.replaceWith(labelElement);
38715            }
38716          }
38717          var labelMessageElement = form.querySelector("label.message[for=\"".concat(element.id, "\"]"));
38718          if (labelMessageElement) {
38719            labelMessageElement.remove();
38720          }
38721        }
38722      }
38723    },
38724    patterns: {
38725      numericality: {
38726        default: /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/,
38727        only_integer: /^[+-]?\d+$/
38728      }
38729    },
38730    selectors: {
38731      inputs: ':input:not(button):not([type="submit"])[name]:visible:enabled',
38732      validate_inputs: ':input:enabled:visible[data-validate]',
38733      forms: 'form[data-client-side-validations]'
38734    },
38735    validators: {
38736      all: function all() {
38737        return jQuery.extend({}, ClientSideValidations.validators.local, ClientSideValidations.validators.remote);
38738      },
38739      local: {},
38740      remote: {}
38741    },
38742    disable: function disable(target) {
38743      var $target = jQuery(target);
38744      $target.off('.ClientSideValidations');
38745      if ($target.is('form')) {
38746        ClientSideValidations.disable($target.find(':input'));
38747      } else {
38748        $target.removeData(['changed', 'valid']);
38749        $target.filter(':input').each(function () {
38750          jQuery(this).removeAttr('data-validate');
38751        });
38752      }
38753    },
38754    reset: function reset(form) {
38755      var $form = jQuery(form);
38756      ClientSideValidations.disable(form);
38757      for (var key in form.ClientSideValidations.settings.validators) {
38758        form.ClientSideValidations.removeError($form.find("[name=\"".concat(key, "\"]")));
38759      }
38760      ClientSideValidations.enablers.form(form);
38761    },
38762    initializeOnEvent: function initializeOnEvent() {
38763      if (window.Turbo != null) {
38764        return 'turbo:load';
38765      } else if (window.Turbolinks != null && window.Turbolinks.supported) {
38766        return window.Turbolinks.EVENTS != null ? 'page:change' : 'turbolinks:load';
38767      }
38768    },
38769    start: function start() {
38770      var initializeOnEvent = ClientSideValidations.initializeOnEvent();
38771      if (initializeOnEvent != null) {
38772        jQuery(document).on(initializeOnEvent, function () {
38773          return jQuery(ClientSideValidations.selectors.forms).validate();
38774        });
38775      } else {
38776        jQuery(function () {
38777          return jQuery(ClientSideValidations.selectors.forms).validate();
38778        });
38779      }
38780    }
38781  };
38782
38783  var absenceLocalValidator = function absenceLocalValidator($element, options) {
38784    var element = $element[0];
38785    if (isValuePresent(element.value)) {
38786      return options.message;
38787    }
38788  };
38789  var presenceLocalValidator = function presenceLocalValidator($element, options) {
38790    var element = $element[0];
38791    if (!isValuePresent(element.value)) {
38792      return options.message;
38793    }
38794  };
38795
38796  var DEFAULT_ACCEPT_OPTION = ['1', true];
38797  var isTextAccepted = function isTextAccepted(value, acceptOption) {
38798    if (!acceptOption) {
38799      acceptOption = DEFAULT_ACCEPT_OPTION;
38800    }
38801    if (Array.isArray(acceptOption)) {
38802      return arrayHasValue(value, acceptOption);
38803    }
38804    return value === acceptOption;
38805  };
38806  var acceptanceLocalValidator = function acceptanceLocalValidator($element, options) {
38807    var element = $element[0];
38808    var valid = true;
38809    if (element.type === 'checkbox') {
38810      valid = element.checked;
38811    }
38812    if (element.type === 'text') {
38813      valid = isTextAccepted(element.value, options.accept);
38814    }
38815    if (!valid) {
38816      return options.message;
38817    }
38818  };
38819
38820  var isMatching = function isMatching(value, regExpOptions) {
38821    return new RegExp(regExpOptions.source, regExpOptions.options).test(value);
38822  };
38823  var hasValidFormat = function hasValidFormat(value, withOptions, withoutOptions) {
38824    return withOptions && isMatching(value, withOptions) || withoutOptions && !isMatching(value, withoutOptions);
38825  };
38826  var formatLocalValidator = function formatLocalValidator($element, options) {
38827    var element = $element[0];
38828    var value = element.value;
38829    if (options.allow_blank && !isValuePresent(value)) {
38830      return;
38831    }
38832    if (!hasValidFormat(value, options.with, options.without)) {
38833      return options.message;
38834    }
38835  };
38836
38837  var VALIDATIONS$1 = {
38838    even: function even(a) {
38839      return parseInt(a, 10) % 2 === 0;
38840    },
38841    greater_than: function greater_than(a, b) {
38842      return parseFloat(a) > parseFloat(b);
38843    },
38844    greater_than_or_equal_to: function greater_than_or_equal_to(a, b) {
38845      return parseFloat(a) >= parseFloat(b);
38846    },
38847    equal_to: function equal_to(a, b) {
38848      return parseFloat(a) === parseFloat(b);
38849    },
38850    less_than: function less_than(a, b) {
38851      return parseFloat(a) < parseFloat(b);
38852    },
38853    less_than_or_equal_to: function less_than_or_equal_to(a, b) {
38854      return parseFloat(a) <= parseFloat(b);
38855    },
38856    odd: function odd(a) {
38857      return parseInt(a, 10) % 2 === 1;
38858    },
38859    other_than: function other_than(a, b) {
38860      return parseFloat(a) !== parseFloat(b);
38861    }
38862  };
38863  var formatValue = function formatValue(element) {
38864    var value = element.value || '';
38865    var numberFormat = element.form.ClientSideValidations.settings.number_format;
38866    return value.trim().replace(new RegExp("\\".concat(numberFormat.separator), 'g'), '.');
38867  };
38868  var getOtherValue = function getOtherValue(validationOption, form) {
38869    if (!isNaN(parseFloat(validationOption))) {
38870      return validationOption;
38871    }
38872    var validationElements = form.querySelectorAll("[name*=\"".concat(validationOption, "\"]"));
38873    if (validationElements.length === 1) {
38874      var validationElement = validationElements[0];
38875      var otherFormattedValue = formatValue(validationElement);
38876      if (!isNaN(parseFloat(otherFormattedValue))) {
38877        return otherFormattedValue;
38878      }
38879    }
38880  };
38881  var isValid = function isValid(validationFunction, validationOption, formattedValue, form) {
38882    if (validationFunction.length === 2) {
38883      var otherValue = getOtherValue(validationOption, form);
38884      return otherValue == null || otherValue === '' || validationFunction(formattedValue, otherValue);
38885    } else {
38886      return validationFunction(formattedValue);
38887    }
38888  };
38889  var runFunctionValidations = function runFunctionValidations(formattedValue, form, options) {
38890    for (var validation in VALIDATIONS$1) {
38891      var validationOption = options[validation];
38892      var validationFunction = VALIDATIONS$1[validation];
38893
38894      // Must check for null because this could be 0
38895      if (validationOption == null) {
38896        continue;
38897      }
38898      if (!isValid(validationFunction, validationOption, formattedValue, form)) {
38899        return options.messages[validation];
38900      }
38901    }
38902  };
38903  var runValidations$1 = function runValidations(formattedValue, form, options) {
38904    if (options.only_integer && !ClientSideValidations.patterns.numericality.only_integer.test(formattedValue)) {
38905      return options.messages.only_integer;
38906    }
38907    if (!ClientSideValidations.patterns.numericality.default.test(formattedValue)) {
38908      return options.messages.numericality;
38909    }
38910    return runFunctionValidations(formattedValue, form, options);
38911  };
38912  var numericalityLocalValidator = function numericalityLocalValidator($element, options) {
38913    var element = $element[0];
38914    var value = element.value;
38915    if (options.allow_blank && !isValuePresent(value)) {
38916      return;
38917    }
38918    var form = element.form;
38919    var formattedValue = formatValue(element);
38920    return runValidations$1(formattedValue, form, options);
38921  };
38922
38923  var VALIDATIONS = {
38924    is: function is(a, b) {
38925      return a === parseInt(b, 10);
38926    },
38927    minimum: function minimum(a, b) {
38928      return a >= parseInt(b, 10);
38929    },
38930    maximum: function maximum(a, b) {
38931      return a <= parseInt(b, 10);
38932    }
38933  };
38934  var runValidations = function runValidations(valueLength, options) {
38935    for (var validation in VALIDATIONS) {
38936      var validationOption = options[validation];
38937      var validationFunction = VALIDATIONS[validation];
38938      if (validationOption && !validationFunction(valueLength, validationOption)) {
38939        return options.messages[validation];
38940      }
38941    }
38942  };
38943  var lengthLocalValidator = function lengthLocalValidator($element, options) {
38944    var element = $element[0];
38945    var value = element.value;
38946    if (options.allow_blank && !isValuePresent(value)) {
38947      return;
38948    }
38949    return runValidations(value.length, options);
38950  };
38951
38952  var isInList = function isInList(value, otherValues) {
38953    var normalizedOtherValues = [];
38954    for (var otherValueIndex in otherValues) {
38955      normalizedOtherValues.push(otherValues[otherValueIndex].toString());
38956    }
38957    return arrayHasValue(value, normalizedOtherValues);
38958  };
38959  var isInRange = function isInRange(value, range) {
38960    return value >= range[0] && value <= range[1];
38961  };
38962  var isIncluded = function isIncluded(value, options, allowBlank) {
38963    if ((options.allow_blank && !isValuePresent(value)) === allowBlank) {
38964      return true;
38965    }
38966    return options.in && isInList(value, options.in) || options.range && isInRange(value, options.range);
38967  };
38968  var exclusionLocalValidator = function exclusionLocalValidator($element, options) {
38969    var element = $element[0];
38970    var value = element.value;
38971    if (isIncluded(value, options, false) || !options.allow_blank && !isValuePresent(value)) {
38972      return options.message;
38973    }
38974  };
38975  var inclusionLocalValidator = function inclusionLocalValidator($element, options) {
38976    var element = $element[0];
38977    var value = element.value;
38978    if (!isIncluded(value, options, true)) {
38979      return options.message;
38980    }
38981  };
38982
38983  var confirmationLocalValidator = function confirmationLocalValidator($element, options) {
38984    var element = $element[0];
38985    var value = element.value;
38986    var confirmationValue = document.getElementById("".concat(element.id, "_confirmation")).value;
38987    if (!options.case_sensitive) {
38988      value = value.toLowerCase();
38989      confirmationValue = confirmationValue.toLowerCase();
38990    }
38991    if (value !== confirmationValue) {
38992      return options.message;
38993    }
38994  };
38995
38996  var isLocallyUnique = function isLocallyUnique(element, value, otherValue, caseSensitive) {
38997    if (!caseSensitive) {
38998      value = value.toLowerCase();
38999      otherValue = otherValue.toLowerCase();
39000    }
39001    if (otherValue === value) {
39002      element.dataset.notLocallyUnique = true;
39003      return false;
39004    }
39005    if (element.dataset.notLocallyUnique) {
39006      delete element.dataset.notLocallyUnique;
39007      element.dataset.changed = true;
39008    }
39009    return true;
39010  };
39011  var uniquenessLocalValidator = function uniquenessLocalValidator($element, options) {
39012    var element = $element[0];
39013    var elementName = element.name;
39014    var matches = elementName.match(/^(.+_attributes\])\[\d+\](.+)$/);
39015    if (!matches) {
39016      return;
39017    }
39018    var form = element.form;
39019    var value = element.value;
39020    var valid = true;
39021    var query = "[name^=\"".concat(matches[1], "\"][name$=\"").concat(matches[2], "\"]:not([name=\"").concat(elementName, "\"])");
39022    var otherElements = form.querySelectorAll(query);
39023    Array.prototype.slice.call(otherElements).forEach(function (otherElement) {
39024      var otherValue = otherElement.value;
39025      if (!isLocallyUnique(otherElement, value, otherValue, options.case_sensitive)) {
39026        valid = false;
39027      }
39028    });
39029    if (!valid) {
39030      return options.message;
39031    }
39032  };
39033
39034  // Validators will run in the following order
39035  ClientSideValidations.validators.local = {
39036    absence: absenceLocalValidator,
39037    presence: presenceLocalValidator,
39038    acceptance: acceptanceLocalValidator,
39039    format: formatLocalValidator,
39040    numericality: numericalityLocalValidator,
39041    length: lengthLocalValidator,
39042    inclusion: inclusionLocalValidator,
39043    exclusion: exclusionLocalValidator,
39044    confirmation: confirmationLocalValidator,
39045    uniqueness: uniquenessLocalValidator
39046  };
39047  jQuery.fn.disableClientSideValidations = function () {
39048    ClientSideValidations.disable(this);
39049    return this;
39050  };
39051  jQuery.fn.enableClientSideValidations = function () {
39052    var _this = this;
39053    var selectors = {
39054      forms: 'form',
39055      inputs: 'input'
39056    };
39057    var _loop = function _loop() {
39058      var enablers = selectors[selector];
39059      _this.filter(ClientSideValidations.selectors[selector]).each(function () {
39060        ClientSideValidations.enablers[enablers](this);
39061      });
39062    };
39063    for (var selector in selectors) {
39064      _loop();
39065    }
39066    return this;
39067  };
39068  jQuery.fn.resetClientSideValidations = function () {
39069    this.filter(ClientSideValidations.selectors.forms).each(function () {
39070      ClientSideValidations.reset(this);
39071    });
39072    return this;
39073  };
39074  jQuery.fn.validate = function () {
39075    this.filter(ClientSideValidations.selectors.forms).each(function () {
39076      jQuery(this).enableClientSideValidations();
39077    });
39078    return this;
39079  };
39080  jQuery.fn.isValid = function (validators) {
39081    var obj = jQuery(this[0]);
39082    if (obj.is('form')) {
39083      return validateForm(obj, validators);
39084    } else {
39085      return validateElement(obj, validatorsFor(this[0].name, validators));
39086    }
39087  };
39088  var cleanNestedElementName = function cleanNestedElementName(elementName, nestedMatches, validators) {
39089    for (var validatorName in validators) {
39090      if (validatorName.match("\\[".concat(nestedMatches[1], "\\].*\\[\\]\\[").concat(nestedMatches[2], "\\]$"))) {
39091        elementName = elementName.replace(/\[[\da-z_]+\]\[(\w+)\]$/g, '[][$1]');
39092      }
39093    }
39094    return elementName;
39095  };
39096  var cleanElementName = function cleanElementName(elementName, validators) {
39097    elementName = elementName.replace(/\[(\w+_attributes)\]\[[\da-z_]+\](?=\[(?:\w+_attributes)\])/g, '[$1][]');
39098    var nestedMatches = elementName.match(/\[(\w+_attributes)\].*\[(\w+)\]$/);
39099    if (nestedMatches) {
39100      elementName = cleanNestedElementName(elementName, nestedMatches, validators);
39101    }
39102    return elementName;
39103  };
39104  var validatorsFor = function validatorsFor(elementName, validators) {
39105    if (Object.prototype.hasOwnProperty.call(validators, elementName)) {
39106      return validators[elementName];
39107    }
39108    return validators[cleanElementName(elementName, validators)] || {};
39109  };
39110  var validateForm = function validateForm($form, validators) {
39111    var valid = true;
39112    $form.trigger('form:validate:before.ClientSideValidations');
39113    $form.find(ClientSideValidations.selectors.validate_inputs).each(function () {
39114      if (!jQuery(this).isValid(validators)) {
39115        valid = false;
39116      }
39117      return true;
39118    });
39119    if (valid) {
39120      $form.trigger('form:validate:pass.ClientSideValidations');
39121    } else {
39122      $form.trigger('form:validate:fail.ClientSideValidations');
39123    }
39124    $form.trigger('form:validate:after.ClientSideValidations');
39125    return valid;
39126  };
39127  var passElement = function passElement($element) {
39128    $element.trigger('element:validate:pass.ClientSideValidations').data('valid', null);
39129  };
39130  var failElement = function failElement($element, message) {
39131    $element.trigger('element:validate:fail.ClientSideValidations', message).data('valid', false);
39132  };
39133  var afterValidate = function afterValidate($element) {
39134    return $element.trigger('element:validate:after.ClientSideValidations').data('valid') !== false;
39135  };
39136  var executeValidator = function executeValidator(validatorFunctions, validatorFunction, validatorOptions, $element) {
39137    for (var validatorOption in validatorOptions) {
39138      if (!validatorOptions[validatorOption]) {
39139        continue;
39140      }
39141      var message = validatorFunction.call(validatorFunctions, $element, validatorOptions[validatorOption]);
39142      if (message) {
39143        failElement($element, message);
39144        return false;
39145      }
39146    }
39147    return true;
39148  };
39149  var executeValidators = function executeValidators(validatorFunctions, $element, validators) {
39150    for (var validator in validators) {
39151      if (!validatorFunctions[validator]) {
39152        continue;
39153      }
39154      if (!executeValidator(validatorFunctions, validatorFunctions[validator], validators[validator], $element)) {
39155        return false;
39156      }
39157    }
39158    return true;
39159  };
39160  var isMarkedForDestroy = function isMarkedForDestroy($element) {
39161    if ($element.attr('name').search(/\[([^\]]*?)\]$/) >= 0) {
39162      var destroyInputName = $element.attr('name').replace(/\[([^\]]*?)\]$/, '[_destroy]');
39163      if (jQuery("input[name=\"".concat(destroyInputName, "\"]")).val() === '1') {
39164        return true;
39165      }
39166    }
39167    return false;
39168  };
39169  var executeAllValidators = function executeAllValidators($element, validators) {
39170    if ($element.data('changed') === false || $element.prop('disabled')) {
39171      return;
39172    }
39173    $element.data('changed', false);
39174    if (executeValidators(ClientSideValidations.validators.all(), $element, validators)) {
39175      passElement($element);
39176    }
39177  };
39178  var validateElement = function validateElement($element, validators) {
39179    $element.trigger('element:validate:before.ClientSideValidations');
39180    if (isMarkedForDestroy($element)) {
39181      passElement($element);
39182    } else {
39183      executeAllValidators($element, validators);
39184    }
39185    return afterValidate($element);
39186  };
39187  if (!window.ClientSideValidations) {
39188    window.ClientSideValidations = ClientSideValidations;
39189    if (!isAMD() && !isCommonJS()) {
39190      ClientSideValidations.start();
39191    }
39192  }
39193  function isAMD() {
39194    return typeof define === 'function' && define.amd; // eslint-disable-line no-undef
39195  }
39196
39197  function isCommonJS() {
39198    return (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined'; // eslint-disable-line no-undef
39199  }
39200
39201  return ClientSideValidations;
39202
39203}));
39204function autoCompleteForm() {
39205	$( "#add-event-name, #edit-event-name" ).autocomplete({ source: '/race_events' });
39206}
39207;
39208// right side widgets and content height, adjust for smaller/laptop screens
39209
39210function contentHeight() { $('.dashboard-main').css({ 'min-height': $('#dashboard-side').height() + 300 }); }
39211
39212// Debounce function to limit rapid calls
39213function debounce(func, wait) {
39214  var timeout;
39215  return function executedFunction() {
39216    var context = this;
39217    var args = arguments;
39218    var later = function() {
39219      timeout = null;
39220      func.apply(context, args);
39221    };
39222    clearTimeout(timeout);
39223    timeout = setTimeout(later, wait);
39224  };
39225}
39226
39227$(document).ready(function () {
39228  if ($(window).height() < 800) {
39229    if ($(window).height() < 600) {
39230      $('#dashboard-side').addClass('much-smaller-screen');
39231    }
39232    else {
39233      $('#dashboard-side').addClass('smaller-screen');
39234    }
39235  }
39236  contentHeight();
39237  // Only initialize best_in_place if elements exist
39238  if (jQuery(".best_in_place").length > 0) {
39239    jQuery(".best_in_place").best_in_place();
39240  }
39241});
39242
39243
39244
39245// Debounced resize handler to prevent excessive contentHeight calculations
39246var debouncedResize = debounce(function() {
39247  if ($(window).height() < 800) {
39248    if ($(window).height() < 600) {
39249      $('#dashboard-side').removeClass('smaller-screen much-smaller-screen').addClass('much-smaller-screen');
39250    }
39251    else {
39252      $('#dashboard-side').removeClass('smaller-screen much-smaller-screen').addClass('smaller-screen');
39253    }
39254  } else {
39255    $('#dashboard-side').removeClass('smaller-screen much-smaller-screen');
39256  }
39257  contentHeight();
39258}, 250);
39259
39260$(window).resize(debouncedResize);
39261
39262function readyForm() {
39263
39264  // Only initialize dropkick if elements exist
39265  if ($('.cselect').length > 0) {
39266    $('.cselect').dropkick();
39267  }
39268
39269  Custom.init();
39270  
39271  // Handle modern race form
39272  if ($('.modern-race-form').length > 0) {
39273
39274    initializeModernRaceForm();
39275    bindNewFormEvents();
39276  } else {
39277    // Legacy form handling
39278    //$('input.star').rating();
39279    //autoCompleteForm();
39280    bindToPace();
39281    calculatePace();
39282  }
39283  
39284
39285}
39286
39287function initializeModernRaceForm() {
39288  // Initialize autocomplete styling
39289  $('.ui-autocomplete').addClass('modern-autocomplete');
39290  
39291  // Parse existing time value on edit
39292  var existingTime = $('#race_result_finish_time').val();
39293  if (existingTime) {
39294    parseTimeInput(existingTime);
39295  }
39296  
39297  // Parse existing location value on edit
39298  var existingLocation = $('#race_result_location_input').val();
39299  if (existingLocation) {
39300    parseLocationInput(existingLocation);
39301  }
39302}
39303
39304function bindNewFormEvents() {
39305  // Remove any existing event handlers to prevent duplicates
39306  $('#race_result_finish_time').off('input blur');
39307  $('#race_result_location_input').off('blur');
39308  $('[name="race_result[amount]"], [name="race_result[unit]"]').off('input change');
39309  $('.star-label').off('click');
39310  
39311  // Time input parsing and validation
39312  $('#race_result_finish_time').on('input blur', function() {
39313    parseTimeInput($(this).val());
39314    calculateModernPace();
39315  });
39316  
39317  // Location input parsing
39318  $('#race_result_location_input').on('blur', function() {
39319    parseLocationInput($(this).val());
39320  });
39321  
39322  // Distance and unit changes
39323  $('[name="race_result[amount]"], [name="race_result[unit]"]').on('input change', function() {
39324    calculateModernPace();
39325  });
39326  
39327  // Star rating clicks
39328  $('.star-label').on('click', function() {
39329    var $input = $(this).prev('.star-input');
39330    var rating = $input.val();
39331    
39332    // Check the radio button
39333    $input.prop('checked', true);
39334    
39335    // Update visual state
39336    highlightStars(rating);
39337  });
39338  
39339  // Star rating hover effects
39340  $('.star-label').on('mouseenter', function() {
39341    var rating = $(this).prev('.star-input').val();
39342    highlightStarsHover(rating);
39343  });
39344  
39345  $('.star-rating').on('mouseleave', function() {
39346    var checkedRating = $('.star-input:checked').val();
39347    if (checkedRating) {
39348      highlightStars(checkedRating);
39349    } else {
39350      $('.star-label').removeClass('selected hover');
39351    }
39352  });
39353}
39354
39355function parseLocationInput(locationString) {
39356  if (!locationString) {
39357    $('#race_result_city_hidden').val('');
39358    $('#race_result_state_hidden').val('');
39359    return;
39360  }
39361  
39362  // Simple parsing: assume "City, State" or "City, State, Country" format
39363  var parts = locationString.split(',').map(function(part) {
39364    return part.trim();
39365  });
39366  
39367  if (parts.length >= 1) {
39368    $('#race_result_city_hidden').val(parts[0]);
39369  }
39370  if (parts.length >= 2) {
39371    $('#race_result_state_hidden').val(parts[1]);
39372  }
39373}
39374
39375function highlightStars(rating) {
39376  $('.star-label').removeClass('selected hover');
39377  for (var i = 1; i <= rating; i++) {
39378    $('#star-' + i).next('.star-label').addClass('selected');
39379  }
39380}
39381
39382function highlightStarsHover(rating) {
39383  $('.star-label').removeClass('hover');
39384  for (var i = 1; i <= rating; i++) {
39385    $('#star-' + i).next('.star-label').addClass('hover');
39386  }
39387}
39388
39389// forms ghost text
39390
39391function clearField(form) {
39392  jQuery(form).find(".addghost").each(function () {
39393    this.defaultValue = this.value;
39394    jQuery(this).click(function () {
39395      if (this.value == this.defaultValue) {
39396        jQuery(this).val("");
39397        jQuery(this).removeClass('ghostt');
39398      }
39399      return false;
39400    });
39401    jQuery(this).focus(function () {
39402      if (this.value == this.defaultValue) {
39403        jQuery(this).val("");
39404        jQuery(this).removeClass('ghostt');
39405      }
39406      return false;
39407    });
39408    jQuery(this).blur(function () {
39409      if (this.value == "") {
39410        jQuery(this).val(this.defaultValue);
39411        jQuery(this).addClass('ghostt');
39412      }
39413    });
39414  });
39415}
39416
39417function clearEmailField(form, email) {
39418  jQuery(form).find(".addghost").each(function () {
39419    if (email !== "Username or email") {
39420
39421      jQuery(this).val(email);
39422      jQuery(this).removeClass('ghostt');
39423    }
39424
39425    jQuery(this).click(function () {
39426      if (this.value == this.defaultValue) {
39427        jQuery(this).val("");
39428        jQuery(this).removeClass('ghostt');
39429      }
39430      return false;
39431    });
39432    jQuery(this).focus(function () {
39433      if (this.value == this.defaultValue) {
39434        jQuery(this).val("");
39435        jQuery(this).removeClass('ghostt');
39436      }
39437      return false;
39438    });
39439    jQuery(this).blur(function () {
39440      if (this.value == "") {
39441        jQuery(this).val(this.defaultValue);
39442        jQuery(this).addClass('ghostt');
39443      }
39444    });
39445  });
39446}
39447
39448// popup boxes - pass popup box ID as parameter (needs "popup-box-bg" empty div that is used as mask)
39449
39450function ShowPopup(element) {
39451  $('.active-popup').removeClass('active-popup').hide();
39452  $('body').addClass('popup-open');
39453  $('#popup-box-bg, #' + element).fadeIn('fast', function () {
39454    $('#' + element).addClass('active-popup');
39455    resetPopupSize(element);
39456  });
39457}
39458
39459function showEdit() {
39460  $("#add-event-notes").fadeIn();
39461  $("#display_note").fadeOut();
39462}
39463
39464function resetPopupSize(element) {
39465
39466  var popupWidth = $('#' + element).width();
39467  var windowWidth = $(window).width();
39468
39469  if (popupWidth > windowWidth) {
39470    $('#' + element).css({
39471      'left': '20px'
39472    });
39473  } else {
39474    var boxMargin = - popupWidth / 2 - 25;
39475    $('#' + element).css({
39476      'margin-left': boxMargin
39477    });
39478  }
39479
39480  var popupHeight = $('#' + element).height();
39481  var windowHeight = $(window).height();
39482
39483  if (popupHeight == 0 || popupHeight > windowHeight - 40) {
39484    $('#' + element).css({
39485      'top': '40px',
39486      'visibility': 'visible'
39487    });
39488  } else {
39489    var boxTop = (windowHeight - popupHeight) / 2;
39490    $('#' + element).css({
39491      'top': boxTop + 'px',
39492      'margin-top': '0',
39493      'visibility': 'visible'
39494    });
39495  }
39496}
39497
39498function hidePopup() {
39499  $('#popup-box-bg, .popup-box-wrapper').fadeOut('fast');
39500  $('.active-popup').removeClass('active-popup');
39501  $('body').removeClass('popup-open');
39502}
39503
39504
39505function showSubDialog(element) {
39506  $('.active-sub-dialog').removeClass('active-sub-dialog').hide();
39507  $('#sub-dialog-box-bg').show();
39508  $('#' + element).fadeIn('fast', function () {
39509    $('#' + element).addClass('active-sub-dialog');
39510    resetPopupSize(element);
39511  });
39512}
39513
39514function hideSubDialog() {
39515  $('#sub-dialog-box-bg, .active-sub-dialog').fadeOut('fast');
39516  $('.active-sub-dialog').removeClass('active-sub-dialog');
39517}
39518
39519// show/hide full gallery
39520
39521function showMore() {
39522  $('#gallery .wrapper').css('height', 'auto');
39523  $('#gallery .more-wrapper').remove();
39524  $('#gallery').append('<div class="more-wrapper"><a class="less" href="javascript:void(0);" onClick="showLess();" title="show less">show less</a></div>');
39525}
39526function showLess() {
39527  $('#gallery .wrapper').css('height', '260px');
39528  $('#gallery .more-wrapper').remove();
39529  $('#gallery').append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="showMore();" title="show more">show more</a></div>');
39530}
39531
39532// show/hide options
39533
39534function showOptions(el, element) {
39535  if ($('#' + element).hasClass('active')) {
39536    $('#' + element + ' .wrapper').css({
39537      'height': '0',
39538      'padding': '0',
39539      'display': 'none'
39540    });
39541    $('#' + element).removeClass('active');
39542    $(el).removeClass('show-options-active');
39543  } else {
39544    $('#' + element + ' .wrapper').css({
39545      'height': 'auto',
39546      'padding': '10px 0',
39547      'display': 'block'
39548    });
39549    $('#' + element).addClass('active');
39550    $(el).addClass('show-options-active');
39551  }
39552}
39553
39554// tooltip
39555
39556function showTooltip(el, element) {
39557  var tipPosition = $(el).offset();
39558  $('#' + element).show(1, function () {
39559    var elementHeight = $(this).height();
39560    $(this).css({
39561      left: tipPosition.left - 93 + 'px',
39562      top: tipPosition.top - elementHeight - 20 + 'px',
39563      visibility: 'visible'
39564    })
39565  });
39566
39567  $(el).mouseout(function () {
39568    $('#' + element).hide().css('visibility', 'hidden');
39569  });
39570}
39571
39572// map tabs
39573
39574function mapTabs(element) {
39575
39576  // set tabs z-index
39577  var number = $('#' + element + ' .map-tabs  li').index() + 1;
39578  $('#' + element + ' .map-tabs li').each(function (n) {
39579    if ($(this).hasClass('current')) {
39580      $(this).css({ 'z-index': number + 2 });
39581    } else {
39582      $(this).css({ 'z-index': number - n });
39583    }
39584    $(this).attr('id', element + 'tabs-0' + n).click(function () {
39585      $('#' + element + ' .map-tabs li').removeClass('current');
39586      $(this).addClass('current');
39587      $('#' + element + ' .race-maps div').css({ 'display': 'none' });
39588      $('#' + element + 'maps-0' + n).css({ 'display': 'block' });
39589    });
39590  });
39591
39592  // on tab click change maps
39593  $('#' + element + ' .race-maps div').each(function (n) {
39594    if (n == 0) { $(this).css({ 'display': 'block' }); }
39595    $(this).attr('id', element + 'maps-0' + n);
39596  });
39597}
39598
39599// user lists (almost same as gallery show more)
39600
39601function listMore(element) {
39602  $('#' + element + ' .wrapper').css('height', 'auto');
39603  $('#' + element + ' .more-wrapper').remove();
39604  $('#' + element).append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="listLess(\'' + element + '\');" title="show less">show less</a></div>');
39605}
39606function listLess(element) {
39607  $('#' + element + ' .wrapper').css('height', '240px');
39608  $('#' + element + ' .more-wrapper').remove();
39609  $('#' + element).append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="listMore(\'' + element + '\');" title="show more">show more</a></div>');
39610}
39611function userList(element) {
39612  var userCount = $('#' + element + ' .wrapper div').length;
39613  if (userCount > 4) {
39614    $('#' + element + ' .wrapper').css('height', '240px');
39615    $('#' + element).append('<div class="more-wrapper"><a class="more" href="javascript:void(0);" onClick="listMore(\'' + element + '\');" title="show more">show more</a></div>');
39616  }
39617}
39618
39619// calendar function - load od date select
39620function calendarResultsFunction(date, gotodate) {
39621  jQuery.easing.def = 'easeInQuart';
39622
39623  var onPageDate = $('div#fr-results-container').find(">:first-child").attr('id');
39624  var newDate = date.split('/');
39625
39626  $("#new_search").find("#search_end_on").val(date);
39627  //search = new Search;
39628  search.execute({ replace: true });
39629}
39630
39631function calendarFunction(date, gotodate) {
39632  jQuery.easing.def = 'easeInQuart';
39633
39634  var onPageDate = $('div#fr-results-container').find(">:first-child").attr('id');
39635  var newDate = date.split('/');
39636
39637  $("#new_search").find("#search_start_on").val(date);
39638  //search = new Search;
39639  search.execute({ replace: true });
39640
39641  // if ( typeof onPageDate == 'undefined') {
39642  //  if (gotodate == true) {
39643  //    $.post( "search/hello", { date_value: date }, function() {
39644  //      $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'none' });
39645  //      clearInterval(CalendarLoadingStart);
39646  //      var target = parseInt(newDate[0]) + '-' + parseInt(newDate[1]);
39647  //      loadResultsF(target);
39648  //    });
39649  //  } else {
39650  //    search = new Search;
39651  //    $("#new_search").find("#search_start_on").val(date);
39652  //    search.execute({ replace: true });
39653  //    // clean page load
39654  //    //$.post( "search/hello", { date_value: date }, function() { loadResultsF(); } );
39655  //  }
39656  // } else {
39657  //  var newDivID = 'month-' + parseInt(newDate[0]) + '-' + parseInt(newDate[2]);
39658  //
39659  //  if ($("div#" + newDivID).length > 0) {
39660  //    var target = parseInt(newDate[0]) + '-' + parseInt(newDate[1]) + '-' + parseInt(newDate[2]);
39661  //    $('html, body').clearQueue().animate({ scrollTop: $('.' + target).offset().top - 50 }, 1000, function (){ scrollAction(); });
39662  //  } else {
39663  //    $('div#fr-results-container').empty();
39664  //    $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' });
39665  //    var currentItem = 1;
39666  //    var CalendarLoadingStart = setInterval(function () {
39667  //      if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; }
39668  //      else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; }
39669  //      else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; }
39670  //    }, 300);
39671  //
39672  //    // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout
39673  //
39674  //    setTimeout(function() {
39675  //      debugger;
39676  //      search = new Search();
39677  //      $.post( "search/hello", { date_value: date }, function() {
39678  //        $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'none' });
39679  //        clearInterval(CalendarLoadingStart);
39680  //        var target = parseInt(newDate[0]) + '-' + parseInt(newDate[1]) + '-' + parseInt(newDate[2]);
39681  //        loadResultsF(target);
39682  //      });
39683  //    }, 1000);
39684  //  }
39685  // }
39686
39687  // function loadResultsF(scrollVar) {
39688  //  window.scrollTo(0, 0);
39689  //  $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' });
39690  //  var currentItem = 1;
39691  //  var CalendarLoadingStart = setInterval(function () {
39692  //    if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; }
39693  //    else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; }
39694  //    else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; }
39695  //  }, 300);
39696  //
39697  //  // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout // remove remove setTimeout
39698  //
39699  //  setTimeout(function() {
39700  //    $.getScript("search/hello", function(data, textStatus, jqxhr) {
39701  //       $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'none' });
39702  //       clearInterval(CalendarLoadingStart);
39703  //       if (scrollVar) {
39704  //        $('html, body').clearQueue().animate({ scrollTop: $('.' + scrollVar).offset().top - 50 }, 1000, function (){ scrollAction(); });
39705  //       } else {
39706  //        scrollAction();
39707  //       }
39708  //
39709  //    });
39710  //  }, 1000);
39711  // }
39712  //
39713  // function scrollAction() {
39714  //  $(window).scroll(function() {
39715  //    var curOffset, oldOffset, sopWorking;
39716  //    oldOffset = $(window).scrollTop();
39717  //    stopWorking = false;
39718  //
39719  //    setInterval(function() {
39720  //      curOffset = $(window).scrollTop();
39721  //      if(curOffset == oldOffset && stopWorking == false) {
39722  //        // stopped scrolling
39723  //        $('li.daylistitem').each(function(index, element) {
39724  //          scrollCalculations(curOffset, element);
39725  //        });
39726  //        stopWorking = true;
39727  //      }
39728  //      oldOffset = curOffset;
39729  //    }, 500);
39730  //  });
39731  //  function scrollCalculations(windowTop, element) {
39732  //    var distance = $(element).offset().top - 60;
39733  //    if ( windowTop >= distance ) {
39734  //      var elementDate = $(element).attr('class').split(' ');
39735  //      var elementDate = elementDate[2];
39736  //      var elementDate = elementDate.split('-');
39737  //      var elementDate = elementDate[0] + '/' + elementDate[1] + '/' + elementDate[2];
39738  //      $("#calendar-widget").datepicker("setDate", elementDate );
39739  //    }
39740  //  }
39741  // }
39742}
39743
39744function showPopup(element) {
39745  $('.active-popup').removeClass('active-popup').hide();
39746  $('#popup-box-bg, #' + element).css('visibility', 'visible').fadeIn('fast', function () {
39747    $('#' + element).addClass('active-popup');
39748    resetPopupSize(element);
39749  });
39750}
39751
39752function resetPopupSize(element) {
39753
39754  var popupWidth = $('#' + element).width();
39755  var windowWidth = $(window).width();
39756
39757  if (popupWidth > windowWidth) {
39758    $('#' + element).css({
39759      'left': '20px'
39760    });
39761  } else {
39762    var boxMargin = - popupWidth / 2 - 25;
39763    $('#' + element).css({
39764      'margin-left': boxMargin
39765    });
39766  }
39767
39768  var popupHeight = $('#' + element).height();
39769  var windowHeight = $(window).height();
39770
39771  if (popupHeight == 0 || popupHeight > windowHeight - 40) {
39772    $('#' + element).css({
39773      'top': '40px',
39774      'visibility': 'visible'
39775    });
39776  } else {
39777    var boxTop = (windowHeight - popupHeight) / 2;
39778    $('#' + element).css({
39779      'top': boxTop + 'px',
39780      'margin-top': '0',
39781      'visibility': 'visible'
39782    });
39783  }
39784}
39785
39786
39787
39788
39789function showLoader(element_id) {
39790  var popupHeight = $(element_id).height();
39791  var popupWidth = $(element_id).width();
39792  if (popupWidth == 0) popupWidth = 400;
39793  if (popupHeight == 0) popupHeight = 200;
39794  $(element_id).html("<div class='loader' style='width: " + popupWidth + "px; height: " + popupHeight + "px;'><img src='https://racemob.s3.amazonaws.com/defaults/loading.gif' /></div>");
39795}
39796
39797function hidePopup() {
39798  // DEPRECATED: Old modal system - just hide any remnants
39799  $('#popup-box-bg, .popup-box-wrapper').hide();
39800  $('.active-popup').removeClass('active-popup');
39801  // Also remove the old modal elements from DOM to prevent interference
39802  $('#popup-box-bg').remove();
39803}
39804
39805function showDialogLink(url, form_params) {
39806  // Determine context from URL
39807  var isLogin = url.includes('/sign_in') || url.includes('/sessions') || url.includes('/login');
39808  var isSignup = url.includes('/registrations') || url.includes('/sign_up') || url.includes('/onboarding');
39809
39810  // Only intercept auth-related URLs - let other URLs work normally
39811  if (isLogin || isSignup) {
39812    // Auth flow: Show the new auth modal or redirect to onboarding
39813    if (window.AuthModal && window.AuthModal.show) {
39814      window.AuthModal.show({
39815        defaultTab: isLogin ? 'signin' : 'signup',
39816        context: {
39817          icon: '🏃',
39818          title: 'Welcome to RaceMob',
39819          subtitle: 'Join our community of runners'
39820        },
39821        title: isLogin ? 'Sign In to Your Account' : 'Create Your RaceMob Account',
39822        message: 'Access your saved races, track results, and connect with other runners'
39823      });
39824    } else {
39825      // Fallback: redirect to the appropriate page
39826      if (isLogin) {
39827        window.location.href = '/sign_in';
39828      } else {
39829        // Use new onboarding flow instead of deprecated /registrations/new
39830        window.location.href = '/onboarding/start';
39831      }
39832    }
39833  } else {
39834    // Non-auth URLs: Use original modal behavior
39835    // Load the URL content into a modal dialog
39836    $.ajax({
39837      url: url,
39838      type: 'GET',
39839      data: form_params || {},
39840      success: function(response) {
39841        // Show the response in a modal
39842        if (typeof response === 'object' && response.html) {
39843          showDialog(response.html);
39844        } else {
39845          showDialog(response);
39846        }
39847      },
39848      error: function() {
39849        console.error('Failed to load dialog content from:', url);
39850      }
39851    });
39852  }
39853
39854  // Hide any existing old popups
39855  $('#popup-box-bg, .popup-box-wrapper').hide();
39856}
39857
39858function showForgotPwd(url, email) {
39859  function isThisSmallerPopup() {
39860    if ($('#popup-dialog #dialog-content').find('.smaller-width-popup').length > 0) {
39861      if (!$('#popup-dialog').hasClass('smaller-width-popup')) {
39862        $('#popup-dialog').addClass('smaller-width-popup-container');
39863        resetPopupSize('popup-dialog');
39864      }
39865    } else {
39866      $('.smaller-width-popup-container').removeClass('smaller-width-popup-container');
39867      resetPopupSize('popup-dialog');
39868    }
39869  }
39870
39871  showPopup('popup-dialog');
39872  showLoader('#popup-dialog #dialog-content');
39873
39874  $.ajax({
39875    url: url,
39876    type: "GET",
39877    data: { email: email },
39878    dataType: 'json',
39879    success: function (result) {
39880      $('#popup-dialog #dialog-content').html(result.html);
39881      isThisSmallerPopup();
39882      resetPopupSize('popup-dialog');
39883      readyForm();
39884      clearEmailField("#forgot form", email);
39885    }
39886  });
39887}
39888
39889
39890
39891function submitDialogForm(form_id) {
39892  form_action = $('#popup-dialog #dialog-content #' + form_id).attr("action");
39893  form_data = $('#popup-dialog #dialog-content #' + form_id).serialize();
39894  form_method = $('#popup-dialog #dialog-content #' + form_id).attr("method");
39895  showLoader('#popup-dialog #dialog-content');
39896  $.ajax({
39897    url: form_action,
39898    type: form_method,
39899    data: form_data,
39900    dataType: 'json',
39901    success: function (response) {
39902      if (response.redirect_to && response.redirect_to != "") {
39903        window.location.href = response.redirect_to;
39904      } else {
39905        $('#popup-dialog #dialog-content').html(response.html);
39906        resetPopupSize('popup-dialog');
39907        readyForm();
39908      }
39909    }
39910  });
39911  return 0;
39912}
39913
39914function showLoader(element_id) {
39915  var popupHeight = $(element_id).height();
39916  var popupWidth = $(element_id).width();
39917  if (popupWidth == 0) popupWidth = 400;
39918  if (popupHeight == 0) popupHeight = 200;
39919  $(element_id).html("<div class='loader' style='width: " + popupWidth + "px; height: " + popupHeight + "px;'><img src='https://racemob.s3.amazonaws.com/defaults/loading.gif' /></div>");
39920}
39921
39922
39923function bindToPace() {
39924  // Modern form binding
39925  if ($('#race_result_finish_time').length > 0) {
39926    $('#race_result_finish_time').on('input blur', function() {
39927      parseTimeInput($(this).val());
39928      calculateModernPace();
39929    });
39930    $("[name='race_result[amount]'], [name='race_result[unit]']").on('input change', function() {
39931      calculateModernPace();
39932    });
39933  } else {
39934    // Legacy form binding
39935    var bindingElements = ["[name='race_result[amount]']", "[name='race_result[unit]']", "[name='race_result[time(4i)]']", "[name='race_result[time(5i)]']", "[name='race_result[time(6i)]']"]
39936    for (var i = 0; i < bindingElements.length; i++) {
39937      $(bindingElements[i]).keyup(function () {
39938        calculatePace();
39939      });
39940      $(bindingElements[i]).blur(function () {
39941        calculatePace();
39942      });
39943    }
39944  }
39945}
39946
39947function calculatePace() {
39948  // Check if we're using the modern form
39949  if ($('#calculated-pace').length > 0) {
39950    calculateModernPace();
39951    return;
39952  }
39953  
39954  // Legacy calculation
39955  var amount = $("[name='race_result[amount]']").val();
39956  var unit = $("[name='race_result[unit]']").val();
39957  var hours = $("[name='race_result[time(4i)]']").val();
39958  var minutes = $("[name='race_result[time(5i)]']").val();
39959  var seconds = $("[name='race_result[time(6i)]']").val();
39960
39961  if (!(amount == "" || unit == "" || hours == "" || minutes == "" || seconds == "")) {
39962    var multiplier = unit == 'mile' ? 1.0 : 0.621371192
39963    var time_in_secs = (parseInt(hours) * 3600) + (parseInt(minutes) * 60) + parseInt(seconds)
39964
39965    var pace = parseInt(time_in_secs / (parseFloat(amount) * multiplier))
39966    var min_comp = parseInt(pace / 60 % 60)
39967    var sec_comp = parseInt(pace % 60 % 60)
39968
39969    $('.mile-pace').html(pad2(min_comp) + ':' + pad2(sec_comp));
39970  } else {
39971    $('.mile-pace').html('MM:SS');
39972  }
39973}
39974
39975function calculateModernPace() {
39976  var amount = $("[name='race_result[amount]']").val();
39977  var unit = $("[name='race_result[unit]']").val();
39978  var hours = $('#race_result_time_4i').val();
39979  var minutes = $('#race_result_time_5i').val();
39980  var seconds = $('#race_result_time_6i').val();
39981
39982  if (amount && unit && hours !== '' && minutes !== '' && seconds !== '') {
39983    var multiplier = unit === 'mile' ? 1.0 : 0.621371192;
39984    var timeInSecs = (parseInt(hours) * 3600) + (parseInt(minutes) * 60) + parseInt(seconds);
39985    var pace = parseInt(timeInSecs / (parseFloat(amount) * multiplier));
39986    var minComp = parseInt(pace / 60 % 60);
39987    var secComp = parseInt(pace % 60 % 60);
39988
39989    $('#calculated-pace').html(pad2(minComp) + ':' + pad2(secComp));
39990  } else {
39991    $('#calculated-pace').html('MM:SS');
39992  }
39993}
39994
39995function parseTimeInput(timeString) {
39996  if (!timeString) {
39997    $('#race_result_time_4i').val('');
39998    $('#race_result_time_5i').val('');
39999    $('#race_result_time_6i').val('');
40000    return;
40001  }
40002  
40003  // Support multiple formats: H:MM:SS, HH:MM:SS, MM:SS (assumes 0 hours)
40004  var timePattern = /^(?:(\d{1,2}):)?(\d{1,2}):(\d{2})$/;
40005  var match = timeString.match(timePattern);
40006  
40007  if (match) {
40008    var hours = match[1] ? parseInt(match[1]) : 0;
40009    var minutes = parseInt(match[2]);
40010    var seconds = parseInt(match[3]);
40011    
40012    // Validate time components
40013    if (hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 && seconds >= 0 && seconds <= 59) {
40014      $('#race_result_time_4i').val(hours.toString().padStart(2, '0'));
40015      $('#race_result_time_5i').val(minutes.toString().padStart(2, '0'));
40016      $('#race_result_time_6i').val(seconds.toString().padStart(2, '0'));
40017      
40018      // Format the display value
40019      $('#race_result_finish_time').val(
40020        hours.toString().padStart(2, '0') + ':' + 
40021        minutes.toString().padStart(2, '0') + ':' + 
40022        seconds.toString().padStart(2, '0')
40023      );
40024    }
40025  }
40026}
40027
40028function pad2(number) {
40029  return (number < 10 ? '0' : '') + number
40030}
40031
40032// Handle clicks on race cards - make entire card clickable except for buttons
40033function handleRaceCardClick(event, card) {
40034  // Check if the click was on a button, link, or interactive element
40035  var target = event.target;
40036  var isInteractiveElement = false;
40037  
40038  // Check if we clicked on or within a button, link, or admin controls
40039  var currentElement = target;
40040  while (currentElement && currentElement !== card) {
40041    if (currentElement.tagName === 'BUTTON' || 
40042        currentElement.tagName === 'A' || 
40043        currentElement.classList.contains('btn-save') ||
40044        currentElement.classList.contains('btn-going') ||
40045        currentElement.classList.contains('admin-inline-controls') ||
40046        currentElement.classList.contains('filter-chip') ||
40047        currentElement.classList.contains('quick-distance-filter') ||
40048        currentElement.hasAttribute('onclick')) {
40049      isInteractiveElement = true;
40050      break;
40051    }
40052    currentElement = currentElement.parentElement;
40053  }
40054  
40055  // If we didn't click on an interactive element, navigate to the race page
40056  if (!isInteractiveElement) {
40057    var raceUrl = card.getAttribute('data-race-url');
40058    if (raceUrl) {
40059      window.location.href = raceUrl;
40060    }
40061  }
40062}
40063
40064// Toggle save race functionality for searches_fast page
40065function toggleSaveRace(button) {
40066  var $button = $(button);
40067  var raceId = $button.data('race-id');
40068  var isSaved = $button.data('saved') === true || $button.data('saved') === 'true';
40069  var url = isSaved ? '/searches_fast/unsave_race' : '/searches_fast/save_race';
40070  var method = isSaved ? 'DELETE' : 'POST';
40071  
40072  // Disable button during request
40073  $button.prop('disabled', true);
40074  
40075  $.ajax({
40076    url: url,
40077    type: method,
40078    data: { 
40079      race_occurence_id: raceId,
40080      authenticity_token: $('meta[name="csrf-token"]').attr('content')
40081    },
40082    dataType: 'json',
40083    success: function(response) {
40084      if (response.success) {
40085        // Toggle the saved state
40086        isSaved = !isSaved;
40087        $button.data('saved', isSaved);
40088        
40089        // Update button appearance
40090        if (isSaved) {
40091          $button.addClass('active');
40092          $button.find('span').text('Saved');
40093          $button.find('svg').attr('fill', 'currentColor');
40094          $button.attr('title', 'Remove from saved races');
40095        } else {
40096          $button.removeClass('active');
40097          $button.find('span').text('Save');
40098          $button.find('svg').attr('fill', 'none');
40099          $button.attr('title', 'Save race for later');
40100        }
40101        
40102        // Show success message if provided
40103        if (response.message) {
40104
40105        }
40106      } else {
40107        // Handle error
40108        showSaveRaceError(response.error || 'Failed to save race. Please try again.');
40109      }
40110    },
40111    error: function(xhr) {
40112      // Check if it's an authentication error
40113      if (xhr.status === 401) {
40114        // Get race details from the button's data attributes
40115        var raceName = $button.closest('.race-card').find('.race-title').text() || 
40116                       $button.data('race-name') || 
40117                       'this race';
40118        var raceDate = $button.closest('.race-card').find('.race-date').text() || 
40119                       $button.data('race-date') || 
40120                       '';
40121        var raceLocation = $button.closest('.race-card').find('.race-location').text() || 
40122                          $button.data('race-location') || 
40123                          '';
40124        
40125        // Show the context-aware auth modal
40126        if (typeof showAuthModalForSaveRace === 'function') {
40127          showAuthModalForSaveRace(raceId, raceName, raceDate, raceLocation);
40128        } else if (window.AuthModal && window.AuthModal.show) {
40129          // Try using AuthModal directly
40130          window.AuthModal.show({
40131            context: {
40132              icon: '💾',
40133              title: 'Save ' + (raceName || 'Race'),
40134              subtitle: raceDate + (raceLocation ? ' • ' + raceLocation : '')
40135            },
40136            title: 'Create Account to Save Races',
40137            message: 'Join RaceMob to save races and get personalized recommendations'
40138          });
40139        } else {
40140          // Last resort - show a better error message
40141
40142          showSaveRaceError('Please sign in to save races. Click the "Sign In" button at the top of the page.');
40143        }
40144      } else {
40145        // Handle other errors
40146        showSaveRaceError('Error: ' + (xhr.responseJSON?.error || 'Failed to save race. Please try again.'));
40147      }
40148    },
40149    complete: function() {
40150      // Re-enable button
40151      $button.prop('disabled', false);
40152    }
40153  });
40154}
40155
40156
40157//function calendarSingleRace(date, search_page){
40158//	$.post( "search/hello", { date_value: date }, function() {
40159//		window.location = search_page;
40160//	});
40161//}
40162
40163// infinite scroll function
40164
40165function infiniteScroll() {
40166
40167  window.search_final = false;
40168  $(window).scroll(function () {
40169    // scroll down
40170    if (($(document).height() - $(window).height() - $(document).scrollTop()) == 0 && !window.search_final) {
40171
40172      var loadingIndicator = $('#loading-more-results-down');
40173      if (!loadingIndicator.is(':visible')) {
40174        $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' });
40175        var currentItem = 1;
40176        var CalendarLoadingStart = setInterval(function () {
40177          if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; }
40178          else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; }
40179          else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; }
40180        }, 300);
40181
40182        var id = $('#fr-results-container').find(">:last-child").attr('id');
40183
40184        search.execute({ append: true });
40185      }
40186    }
40187  });
40188}
40189
40190// Append Next Results
40191function appendNext() {
40192
40193  window.search_final = false;
40194
40195  $('.load-races-wrapper').addClass('hidden').hide();
40196  var loadingIndicator = $('#loading-more-results-down');
40197  if (!loadingIndicator.is(':visible')) {
40198    $('#loading-more-results-down, #calendar-loading-mask').css({ 'display': 'block' });
40199    var currentItem = 1;
40200    var CalendarLoadingStart = setInterval(function () {
40201      if (currentItem == 1) { $('#calendar-loading-icon').css({ 'background-position': '0px 0px' }); currentItem++; }
40202      else if (currentItem == 2) { $('#calendar-loading-icon').css({ 'background-position': '-66px 0px' }); currentItem++; }
40203      else if (currentItem == 3) { $('#calendar-loading-icon').css({ 'background-position': '-132px 0px' }); currentItem = 1; }
40204    }, 300);
40205
40206    var id = $('#fr-results-container').find(">:last-child").attr('id');
40207
40208    search.execute({ append: true });
40209  }
40210}
40211
40212// // coupon page, add/remove coupons (clone fields)
40213
40214// jQuery(function () {
40215//   $('form').on('click', '.remove_fields', function (event) {
40216//     $(this).prev('input[type=hidden]').val('1');
40217//     $(this).closest('div').hide();
40218//     return event.preventDefault();
40219//   });
40220//   return $('form').on('click', '.add_fields', function (event) {
40221//     var regexp, time;
40222//     time = new Date().getTime();
40223//     regexp = new RegExp($(this).data('id'), 'g');
40224//     $(this).before($(this).data('fields').replace(regexp, time));
40225//     return event.preventDefault();
40226//   });
40227// });
40228
40229// $("input[type=hidden][id$='_destroy'][value='true']").closest("ITEM SELECTOR").hide();
40230
40231
40232// function cloneFormGroup(element) {
40233//   //	$('#' + element).wrapInner('<div class="clone-group-item" />');
40234//   //	$('#' + element).after('<a class="add-group" href="javascript:void(0);" onClick="addGroup(\'' + element + '\');" title="Add more">Add more</a>');
40235// }
40236// function addGroup(element) {
40237//   var numItems = $('.clone-group-item').length - 1;
40238//   if (numItems > 0) {
40239//     var groupItem = $('.clone-group-item-' + numItems).clone();
40240//     $('#' + element).append(groupItem);
40241//   } else {
40242//     var groupItem = $('.clone-group-item').clone();
40243//     $('#' + element).append(groupItem);
40244//   }
40245//   countAndSortGroups('add', numItems + 1);
40246// }
40247// function removeGroup(element) {
40248//   $('.' + element).remove();
40249//   var numItems = $('.clone-group-item').length - 1;
40250//   countAndSortGroups('remove', numItems);
40251//   if (numItems == 0) {
40252//     $('.clone-group-item').find('.remove-group').remove();
40253//   }
40254// }
40255// function countAndSortGroups(addOrRemove, numItems) {
40256//   $('.clone-group-item').each(function (index) {
40257//     if (numItems > 0) {
40258//       $(this).find('.remove-group').remove();
40259//       $(this).append('<a class="remove-group" href="javascript:void(0);" onClick="removeGroup(\'clone-group-item-' + index + '\');" title="Remove">Remove</a>');
40260//     }
40261//     $(this).removeClass().addClass('clone-group-item clone-group-item-' + index);
40262//     var currentIndex = index;
40263//     $('*', this).each(function () {
40264//       var attId = $(this).attr('id');
40265//       var attName = $(this).attr('name');
40266//       var attFor = $(this).attr('for');
40267//       if (attId) {
40268//         var attIdSplit = attId.split('-cin');
40269//         $(this).attr('id', attIdSplit[0] + '-cin' + currentIndex);
40270//       }
40271//       if (attName) {
40272//         var attNameSplit = attName.split('-cin');
40273//         $(this).attr('name', attNameSplit[0] + '-cin' + currentIndex);
40274//       }
40275//       if (attFor) {
40276//         var attForSplit = attFor.split('-cin');
40277//         $(this).attr('for', attForSplit[0] + '-cin' + currentIndex);
40278//       }
40279//       if (addOrRemove == 'add' && currentIndex == numItems && numItems > 0) {
40280//         $(this).val("");
40281//       }
40282//       if (currentIndex != 0) {
40283//         $(this).removeClass('addghost').removeClass('ghostt');
40284//       }
40285//     });
40286//   });
40287// }
40288
40289// Helper function for save race error notifications
40290function showSaveRaceError(message) {
40291  if (window.UIHelpers && window.UIHelpers.showNotification) {
40292    window.UIHelpers.showNotification('error', 'Save Race Error', message);
40293  } else if (window.showNotification) {
40294    window.showNotification('error', 'Save Race Error', message);
40295  } else {
40296    console.error('Save Race Error:', message);
40297    alert(message); // Keep as fallback
40298  }
40299}
40300;
40301// Geolocation Helper - Reusable location detection functionality
40302// This can be used across different parts of the application
40303
40304window.GeolocationHelper = {
40305  /**
40306   * Show error message using notification system
40307   * @param {string} message - Error message
40308   */
40309  showError: function(message) {
40310    if (window.UIHelpers && window.UIHelpers.showNotification) {
40311      window.UIHelpers.showNotification('error', 'Location Error', message);
40312    } else if (window.showNotification) {
40313      window.showNotification('error', 'Location Error', message);
40314    } else {
40315      console.error('Location Error:', message);
40316      alert(message); // Keep as fallback
40317    }
40318  },
40319  
40320  // Get current position and reverse geocode it
40321  getCurrentLocation: function(options) {
40322    options = options || {};
40323    
40324    return new Promise(function(resolve, reject) {
40325      // Check if browser supports geolocation
40326      if (!navigator.geolocation) {
40327        reject(new Error('Geolocation is not supported by your browser'));
40328        return;
40329      }
40330      
40331      // Request current position
40332      navigator.geolocation.getCurrentPosition(
40333        function(position) {
40334          // Success - got coordinates
40335          const lat = position.coords.latitude;
40336          const lng = position.coords.longitude;
40337          
40338
40339          
40340          // Reverse geocode the coordinates
40341          GeolocationHelper.reverseGeocode(lat, lng)
40342            .then(function(location) {
40343              resolve({
40344                latitude: lat,
40345                longitude: lng,
40346                location: location.location,
40347                formatted_location: location.formatted_location,
40348                city: location.city,
40349                state: location.state
40350              });
40351            })
40352            .catch(function(error) {
40353              // Return coordinates even if reverse geocoding fails
40354              resolve({
40355                latitude: lat,
40356                longitude: lng,
40357                location: null,
40358                formatted_location: null,
40359                error: 'Reverse geocoding failed'
40360              });
40361            });
40362        },
40363        function(error) {
40364          // Error callback
40365
40366          
40367          let message = 'Could not get your location. ';
40368          switch(error.code) {
40369            case error.PERMISSION_DENIED:
40370              message += 'Please enable location access in your browser settings.';
40371              break;
40372            case error.POSITION_UNAVAILABLE:
40373              message += 'Location information is unavailable.';
40374              break;
40375            case error.TIMEOUT:
40376              message += 'Location request timed out.';
40377              break;
40378            default:
40379              message += 'An unknown error occurred.';
40380          }
40381          
40382          reject(new Error(message));
40383        },
40384        {
40385          // Options
40386          enableHighAccuracy: options.enableHighAccuracy !== false,
40387          timeout: options.timeout || 10000,
40388          maximumAge: options.maximumAge || 0
40389        }
40390      );
40391    });
40392  },
40393  
40394  // Reverse geocode coordinates to location name
40395  reverseGeocode: function(latitude, longitude) {
40396    return fetch('/api/reverse_geocode', {
40397      method: 'POST',
40398      headers: {
40399        'Content-Type': 'application/json',
40400        'Accept': 'application/json',
40401        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
40402      },
40403      body: JSON.stringify({
40404        latitude: latitude,
40405        longitude: longitude
40406      })
40407    })
40408    .then(function(response) {
40409      if (!response.ok) {
40410        throw new Error('Network response was not ok');
40411      }
40412      return response.json();
40413    })
40414    .then(function(data) {
40415      if (data.success && data.location) {
40416
40417        return {
40418          location: data.location,
40419          formatted_location: data.formatted_location || data.location,
40420          city: data.city,
40421          state: data.state
40422        };
40423      } else {
40424        throw new Error(data.error || 'Could not determine location');
40425      }
40426    });
40427  },
40428  
40429  // Initialize locate button with default behavior
40430  initLocateButton: function(buttonId, inputId, options) {
40431    options = options || {};
40432    
40433    const locateBtn = document.getElementById(buttonId || 'locate-me-btn');
40434    const searchInput = document.getElementById(inputId || 'smart_search_hero');
40435    
40436    if (!locateBtn || !searchInput) {
40437
40438      return;
40439    }
40440    
40441    locateBtn.addEventListener('click', function(e) {
40442      e.preventDefault();
40443      
40444      // Show loading state
40445      locateBtn.classList.add('loading');
40446      const originalTitle = locateBtn.title;
40447      locateBtn.title = 'Locating...';
40448      
40449      // Get current location
40450      GeolocationHelper.getCurrentLocation(options)
40451        .then(function(result) {
40452          // Update search input with formatted location
40453          searchInput.value = result.formatted_location || result.location || '';
40454          
40455          // Remove loading state
40456          locateBtn.classList.remove('loading');
40457          locateBtn.title = originalTitle;
40458          
40459          // Show success feedback
40460          locateBtn.style.color = '#059669';
40461          setTimeout(function() {
40462            locateBtn.style.color = '';
40463          }, 2000);
40464          
40465          // Call custom success callback if provided
40466          if (options.onSuccess) {
40467            options.onSuccess(result);
40468          }
40469          
40470          // Auto-submit form if specified
40471          if (options.autoSubmit) {
40472            const form = searchInput.closest('form');
40473            if (form) {
40474              form.submit();
40475            }
40476          }
40477        })
40478        .catch(function(error) {
40479
40480          
40481          // Remove loading state
40482          locateBtn.classList.remove('loading');
40483          locateBtn.title = originalTitle;
40484          
40485          // Call custom error callback if provided
40486          if (options.onError) {
40487            options.onError(error);
40488          } else {
40489            // Default error handling. Use the GeolocationHelper reference, NOT
40490            // `this` — inside this .catch callback `this` is not the helper
40491            // object (it's undefined/window), so `this.showError` threw
40492            // "TypeError: this.showError is not a function" (geolocation_helper.js:186).
40493            // Matches the `GeolocationHelper.getCurrentLocation` call above.
40494            GeolocationHelper.showError(error.message);
40495          }
40496        });
40497    });
40498  },
40499  
40500  // Check if geolocation is available
40501  isAvailable: function() {
40502    return 'geolocation' in navigator;
40503  },
40504  
40505  // Request permission (for future use when Permissions API is more widely supported)
40506  requestPermission: function() {
40507    if ('permissions' in navigator) {
40508      return navigator.permissions.query({ name: 'geolocation' })
40509        .then(function(result) {
40510          return result.state;
40511        });
40512    }
40513    return Promise.resolve('unknown');
40514  }
40515};
40516
40517// Initialize on page load if on searches_fast page
40518document.addEventListener('DOMContentLoaded', function() {
40519  // Auto-initialize for searches_fast pages
40520  if (window.location.pathname.includes('/searches_fast') || 
40521      window.location.pathname.includes('/running_events') ||
40522      window.location.pathname.includes('/triathlons') ||
40523      window.location.pathname.includes('/fun_races') ||
40524      window.location.pathname.includes('/duathlons')) {
40525    
40526    GeolocationHelper.initLocateButton('locate-me-btn', 'location_search_hero', {
40527      autoSubmit: false,
40528      enableHighAccuracy: true,
40529      onSuccess: function(result) {
40530        // Update floating label when location is detected
40531        if (window.LocationAutocomplete && window.LocationAutocomplete.updateFloatingLabel) {
40532          var input = document.getElementById('location_search_hero');
40533          if (input) {
40534            window.LocationAutocomplete.updateFloatingLabel(input);
40535          }
40536        }
40537      }
40538    });
40539  }
40540});
40541/**
40542 * RaceMap - Interactive map functionality for search results
40543 * Integrates with existing UnifiedFilterHandlers for seamless filter updates
40544 */
40545
40546// Enable map functionality for view switching
40547window.isMapEnabled = true;
40548
40549window.RaceMap = {
40550  map: null,
40551  markers: [],
40552  markerCluster: null,
40553
40554  /**
40555   * Initialize the Leaflet map
40556   */
40557  init: function () {
40558    // console.log('RaceMap: init() called');
40559    const mapElement = document.getElementById('race-map');
40560    if (!mapElement) {
40561      console.error('RaceMap: Map element #race-map not found');
40562      return;
40563    }
40564
40565    // console.log('RaceMap: Map element found:', mapElement);
40566
40567    // Check if Leaflet is available
40568    if (typeof L === 'undefined') {
40569      console.error('RaceMap: Leaflet library not loaded - typeof L:', typeof L);
40570      // console.log('RaceMap: Attempting to dynamically load Leaflet...');
40571      this.loadLeafletDynamically();
40572      return;
40573    }
40574
40575    // console.log('RaceMap: Leaflet library available:', L.version);
40576
40577    // Prevent re-initialization
40578    if (this.map) {
40579      // console.log('RaceMap: Map already initialized, refreshing size...');
40580      setTimeout(() => this.map.invalidateSize(), 100);
40581      return;
40582    }
40583
40584    // console.log('RaceMap: Initializing new map instance...');
40585
40586    // Default center - will be updated based on search coordinates or markers
40587    let defaultCenter = [37.7749, -122.4194]; // San Francisco fallback
40588    let defaultZoom = 11;
40589
40590    // Use geocoded search coordinates if available (most accurate)
40591    if (window.searchCoordinates && Array.isArray(window.searchCoordinates) && window.searchCoordinates.length === 2) {
40592      defaultCenter = [window.searchCoordinates[1], window.searchCoordinates[0]]; // Convert [lng, lat] to [lat, lng] for Leaflet
40593      // console.log('RaceMap: Using geocoded search coordinates:', defaultCenter);
40594    } else if (window.searchLocation) {
40595      // console.log('RaceMap: Geocoded coordinates not available, falling back to location mapping for:', window.searchLocation);
40596
40597      // Fallback to simple location mapping for common cities
40598      const locationMap = {
40599        'boston': [42.3601, -71.0589],
40600        'new_york': [40.7128, -74.0060],
40601        'chicago': [41.8781, -87.6298],
40602        'los_angeles': [34.0522, -118.2437],
40603        'san_francisco': [37.7749, -122.4194],
40604        'seattle': [47.6062, -122.3321],
40605        'denver': [39.7392, -104.9903],
40606        'atlanta': [33.7490, -84.3880],
40607        'miami': [25.7617, -80.1918],
40608        'austin': [30.2672, -97.7431]
40609      };
40610
40611      const locationKey = window.searchLocation.toLowerCase().replace(/[\s,]/g, '_');
40612      if (locationMap[locationKey]) {
40613        defaultCenter = locationMap[locationKey];
40614        // console.log('RaceMap: Using mapped coordinates for', window.searchLocation, ':', defaultCenter);
40615      }
40616    } else {
40617      // console.log('RaceMap: No search location or coordinates available, using San Francisco default');
40618    }
40619
40620    try {
40621      // Initialize map
40622      this.map = L.map('race-map').setView(defaultCenter, defaultZoom);
40623
40624      // Add Carto Voyager tiles (free, no API key needed)
40625      L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
40626        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>',
40627        subdomains: 'abcd',
40628        maxZoom: 19
40629      }).addTo(this.map);
40630      
40631      // Remove default Leaflet attribution and add custom one with only required attributions
40632      this.map.attributionControl.remove();
40633      
40634      // Add custom attribution control with only OpenStreetMap and CARTO
40635      L.control.attribution({
40636        prefix: false // Remove "Leaflet" prefix
40637      }).addTo(this.map);
40638
40639      // Add markers from window.mapData
40640      if (window.mapData && window.mapData.length > 0) {
40641        this.addMarkers(window.mapData);
40642      } else {
40643        // console.log('RaceMap: No map data available');
40644      }
40645
40646      // console.log('RaceMap: Map initialized successfully');
40647    } catch (error) {
40648      console.error('RaceMap: Failed to initialize map:', error);
40649      this.showError('Failed to load map. Please refresh the page.');
40650    }
40651  },
40652
40653  /**
40654   * Add markers to the map with clustering for performance
40655   */
40656  addMarkers: function (markerData) {
40657    if (!this.map) {
40658      console.warn('RaceMap: Map not initialized');
40659      return;
40660    }
40661
40662    // console.log(`RaceMap: Adding ${markerData.length} markers`);
40663
40664    // Clear existing markers
40665    this.clearMarkers();
40666
40667    if (markerData.length === 0) {
40668      // console.log('RaceMap: No markers to add');
40669      return;
40670    }
40671
40672    // Use clustering for 20+ markers to improve performance
40673    if (markerData.length >= 20) {
40674      // Only create a new cluster if one doesn't exist
40675      if (!this.markerCluster) {
40676        this.markerCluster = L.markerClusterGroup({
40677          maxClusterRadius: 50,
40678          spiderfyOnMaxZoom: true,
40679          showCoverageOnHover: false,
40680          zoomToBoundsOnClick: true
40681        });
40682        this.map.addLayer(this.markerCluster);
40683      }
40684
40685      markerData.forEach(data => {
40686        if (this.isValidMarkerData(data)) {
40687          const marker = this.createMarker(data);
40688          this.markerCluster.addLayer(marker);
40689          this.markers.push(marker); // Push individual markers, not the cluster
40690        }
40691      });
40692    } else {
40693      // Add markers directly for small sets
40694      markerData.forEach(data => {
40695        if (this.isValidMarkerData(data)) {
40696          const marker = this.createMarker(data);
40697          marker.addTo(this.map);
40698          this.markers.push(marker);
40699        }
40700      });
40701    }
40702
40703    // Auto-fit map to show all markers
40704    this.fitBounds(markerData);
40705
40706    // Update marker count display
40707    this.updateMarkerCount(markerData.length);
40708  },
40709
40710  /**
40711   * Validate marker data
40712   */
40713  isValidMarkerData: function (data) {
40714    return data &&
40715      typeof data.lat === 'number' &&
40716      typeof data.lng === 'number' &&
40717      !isNaN(data.lat) &&
40718      !isNaN(data.lng) &&
40719      data.lat >= -90 && data.lat <= 90 &&
40720      data.lng >= -180 && data.lng <= 180;
40721  },
40722
40723  /**
40724   * Create a single marker with popup
40725   */
40726  createMarker: function (data) {
40727    const marker = L.marker([data.lat, data.lng]);
40728
40729    // Create popup content
40730    const popupContent = this.buildPopup(data);
40731    marker.bindPopup(popupContent, {
40732      maxWidth: 250,
40733      className: 'race-map-popup'
40734    });
40735
40736    return marker;
40737  },
40738
40739  /**
40740   * Build popup HTML for a race marker
40741   */
40742  buildPopup: function (data) {
40743    return `
40744      <div class="race-map-popup-content">
40745        <h4 style="margin: 0 0 8px 0; font-size: 16px; font-weight: 600; color: #1a1a1a; line-height: 1.3;">
40746          ${this.escapeHtml(data.name || 'Unnamed Race')}
40747        </h4>
40748        <p style="margin: 0 0 12px 0; color: #6b7280; font-size: 14px;">
40749          ${this.escapeHtml(data.date || 'Date TBD')} • ${this.escapeHtml(data.location || 'Location TBD')}
40750        </p>
40751        <div style="margin-top: 12px;">
40752          <a href="${data.url || '#'}" 
40753             style="display: inline-block; padding: 8px 16px; background-color: #f97316; color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 500; transition: background-color 0.2s;"
40754             onmouseover="this.style.backgroundColor='#ea580c'"
40755             onmouseout="this.style.backgroundColor='#f97316'">
40756            View Race Details
40757          </a>
40758        </div>
40759      </div>
40760    `;
40761  },
40762
40763  /**
40764   * Simple HTML escape function
40765   */
40766  escapeHtml: function (text) {
40767    if (!text) return '';
40768    const map = {
40769      '&': '&amp;',
40770      '<': '&lt;',
40771      '>': '&gt;',
40772      '"': '&quot;',
40773      "'": '&#039;'
40774    };
40775    return text.toString().replace(/[&<>"']/g, function (m) { return map[m]; });
40776  },
40777
40778  /**
40779   * Clear all markers from the map
40780   */
40781  clearMarkers: function () {
40782    if (this.markerCluster) {
40783      this.map.removeLayer(this.markerCluster);
40784      this.markerCluster = null;
40785    }
40786
40787    this.markers.forEach(marker => {
40788      if (marker && this.map) {
40789        this.map.removeLayer(marker);
40790      }
40791    });
40792    this.markers = [];
40793  },
40794
40795  /**
40796   * Fit map bounds to show all markers
40797   */
40798  fitBounds: function (markerData) {
40799    if (!markerData || markerData.length === 0) return;
40800
40801    // init() constructs L.map while #race-map-container is still display:none (the map view is
40802    // hidden until the toggle), so Leaflet caches _size = [0,0]. getBoundsZoom then divides by
40803    // a zero viewport and returns a garbage zoom (19), which maxZoom clamps to exactly 13 — so
40804    // the failure looks like a deliberate "zoom to 13" rather than a crash, and every marker
40805    // lands off-screen while the counter still says "Showing N races". show()'s 100ms
40806    // setTimeout(invalidateSize) lands AFTER this and nothing re-fits. Re-measure here: this is
40807    // the only place zoom is computed, and it covers first open, filter swaps and load-more.
40808    if (this.map) this.map.invalidateSize(false); // false: no pan animation to race the setView
40809
40810    const validMarkers = markerData.filter(data => this.isValidMarkerData(data));
40811
40812    if (validMarkers.length === 0) return;
40813
40814    if (validMarkers.length === 1) {
40815      // Single marker - center on it with reasonable zoom
40816      const marker = validMarkers[0];
40817      this.map.setView([marker.lat, marker.lng], 12);
40818    } else {
40819      // Multiple markers - fit bounds
40820      const bounds = L.latLngBounds();
40821      validMarkers.forEach(data => {
40822        bounds.extend([data.lat, data.lng]);
40823      });
40824
40825      // Add padding and set bounds
40826      this.map.fitBounds(bounds, {
40827        padding: [20, 20],
40828        maxZoom: 13 // Don't zoom in too much even for close markers
40829      });
40830    }
40831  },
40832
40833  /**
40834   * Update markers with new data (replace all markers)
40835   */
40836  updateMarkers: function (newMarkerData) {
40837    // console.log(`RaceMap: Updating with ${newMarkerData ? newMarkerData.length : 0} markers`);
40838
40839    if (!newMarkerData) {
40840      newMarkerData = [];
40841    }
40842
40843    // Update window.mapData for consistency
40844    window.mapData = newMarkerData;
40845
40846    // Clear existing markers first
40847    this.clearMarkers();
40848
40849    // Add new markers
40850    this.addMarkers(newMarkerData);
40851  },
40852
40853  /**
40854   * Append markers to existing ones (for Load More)
40855   */
40856  appendMarkers: function (newMarkerData) {
40857    // console.log(`RaceMap: Appending ${newMarkerData ? newMarkerData.length : 0} new markers`);
40858
40859    if (!newMarkerData || newMarkerData.length === 0) {
40860      return;
40861    }
40862
40863    if (!this.map) {
40864      console.warn('RaceMap: Map not initialized');
40865      return;
40866    }
40867
40868    // Add to existing data
40869    if (!window.mapData) {
40870      window.mapData = [];
40871    }
40872    window.mapData = window.mapData.concat(newMarkerData);
40873
40874    // Add new markers to existing cluster or map
40875    if (this.markerCluster) {
40876      // Add to existing cluster
40877      newMarkerData.forEach(data => {
40878        if (this.isValidMarkerData(data)) {
40879          const marker = this.createMarker(data);
40880          this.markerCluster.addLayer(marker);
40881        }
40882      });
40883    } else {
40884      // Add directly to map
40885      newMarkerData.forEach(data => {
40886        if (this.isValidMarkerData(data)) {
40887          const marker = this.createMarker(data);
40888          marker.addTo(this.map);
40889          this.markers.push(marker);
40890        }
40891      });
40892    }
40893
40894    // Update marker count display with total count
40895    this.updateMarkerCount(window.mapData.length);
40896  },
40897
40898  /**
40899   * Clear all markers from map
40900   */
40901  clearMarkers: function () {
40902    // console.log(`RaceMap: Clearing ${this.markers.length} existing markers`);
40903
40904    // Remove markers from cluster AND from map
40905    if (this.markerCluster) {
40906      this.markerCluster.clearLayers();
40907      this.map.removeLayer(this.markerCluster);
40908      this.markerCluster = null; // Set to null, will be recreated in addMarkers if needed
40909    }
40910
40911    // Remove individual markers if any
40912    this.markers.forEach(marker => {
40913      if (marker && marker !== this.markerCluster) {
40914        this.map.removeLayer(marker);
40915      }
40916    });
40917
40918    // Clear the markers array
40919    this.markers = [];
40920    // console.log('RaceMap: All markers cleared');
40921  },
40922
40923  /**
40924   * Update the marker count display
40925   */
40926  updateMarkerCount: function (count) {
40927    const countElement = document.querySelector('#map-race-count');
40928    if (countElement) {
40929      countElement.textContent = count;
40930    }
40931  },
40932
40933  /**
40934   * Show the map and ensure it's properly sized
40935   */
40936  show: function () {
40937    // console.log('RaceMap: show() called');
40938    const container = document.getElementById('race-map-container');
40939    if (container) {
40940      container.style.display = 'block';
40941      // console.log('RaceMap: Container made visible');
40942
40943      // Initialize map if not already done
40944      if (!this.map) {
40945        // console.log('RaceMap: Map not initialized, calling init()');
40946        this.init();
40947      } else {
40948        // console.log('RaceMap: Map already initialized, refreshing size');
40949        // Refresh map size after showing
40950        setTimeout(() => {
40951          if (this.map) {
40952            this.map.invalidateSize();
40953            // console.log('RaceMap: Map size invalidated');
40954          }
40955        }, 100);
40956      }
40957    } else {
40958      console.error('RaceMap: Map container not found');
40959    }
40960  },
40961
40962  /**
40963   * Hide the map
40964   */
40965  hide: function () {
40966    const container = document.getElementById('race-map-container');
40967    if (container) {
40968      container.style.display = 'none';
40969    }
40970  },
40971
40972  /**
40973   * Show error message to user
40974   */
40975  showError: function (message) {
40976    const mapElement = document.getElementById('race-map');
40977    if (mapElement) {
40978      mapElement.innerHTML = `
40979        <div style="display: flex; align-items: center; justify-content: center; height: 400px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px;">
40980          <div style="text-align: center; padding: 20px;">
40981            <div style="color: #dc3545; font-size: 18px; margin-bottom: 10px;">⚠️ Map Unavailable</div>
40982            <div style="color: #6c757d; font-size: 14px;">${message}</div>
40983          </div>
40984        </div>
40985      `;
40986    }
40987  },
40988
40989  /**
40990   * Dynamically load Leaflet if not available (fallback)
40991   */
40992  loadLeafletDynamically: function () {
40993    // console.log('RaceMap: Loading Leaflet dynamically...');
40994
40995    // Return a Promise for proper async handling
40996    return new Promise((resolve, reject) => {
40997      // Check if already loaded
40998      if (window.L && window.L.markerClusterGroup) {
40999        window.leafletLoaded = true;
41000        resolve();
41001        return;
41002      }
41003
41004      // Load CSS
41005      const css = document.createElement('link');
41006      css.rel = 'stylesheet';
41007      css.href = 'https://unpkg.com/[email protected]/dist/leaflet.css';
41008      document.head.appendChild(css);
41009
41010      // Load MarkerCluster CSS
41011      const clusterCss = document.createElement('link');
41012      clusterCss.rel = 'stylesheet';
41013      clusterCss.href = 'https://unpkg.com/[email protected]/dist/MarkerCluster.css';
41014      document.head.appendChild(clusterCss);
41015
41016      const clusterDefaultCss = document.createElement('link');
41017      clusterDefaultCss.rel = 'stylesheet';
41018      clusterDefaultCss.href = 'https://unpkg.com/[email protected]/dist/MarkerCluster.Default.css';
41019      document.head.appendChild(clusterDefaultCss);
41020
41021      // Load Leaflet JS
41022      const leafletScript = document.createElement('script');
41023      leafletScript.src = 'https://unpkg.com/[email protected]/dist/leaflet.js';
41024      const self = this; // Capture scope for callbacks
41025      leafletScript.onload = () => {
41026        // console.log('RaceMap: Leaflet loaded dynamically');
41027
41028        // Load MarkerCluster JS
41029        const clusterScript = document.createElement('script');
41030        clusterScript.src = 'https://unpkg.com/[email protected]/dist/leaflet.markercluster.js';
41031        clusterScript.onload = () => {
41032          // console.log('RaceMap: MarkerCluster loaded dynamically');
41033          window.leafletLoaded = true;
41034          // Retry initialization
41035          self.init();
41036          resolve();
41037        };
41038        clusterScript.onerror = () => {
41039          console.error('RaceMap: Failed to load MarkerCluster dynamically');
41040          self.showError('Failed to load map dependencies. Please refresh the page.');
41041          reject(new Error('Failed to load MarkerCluster'));
41042        };
41043        document.head.appendChild(clusterScript);
41044      };
41045      leafletScript.onerror = () => {
41046        console.error('RaceMap: Failed to load Leaflet dynamically');
41047        self.showError('Failed to load map library. Please refresh the page.');
41048        reject(new Error('Failed to load Leaflet'));
41049      };
41050      document.head.appendChild(leafletScript);
41051    });
41052  },
41053
41054  /**
41055   * Preload Leaflet resources when browser is idle (for performance)
41056   */
41057  preloadWhenIdle: function() {
41058    if (window.requestIdleCallback) {
41059      requestIdleCallback(() => {
41060        this.loadLeafletDynamically().catch(console.error);
41061      }, { timeout: 5000 });
41062    } else {
41063      // Fallback for browsers without requestIdleCallback
41064      setTimeout(() => {
41065        this.loadLeafletDynamically().catch(console.error);
41066      }, 3000);
41067    }
41068  }
41069};
41070
41071// REMOVED: Duplicate switchView implementation
41072// View switching is now entirely handled by UnifiedFilterHandlers
41073// This was causing conflicts and preventing the proper function from running
41074
41075/**
41076 * Listen for filter updates - markers now come from UnifiedFilterHandlers
41077 */
41078$(document).on('filtersUpdated', function (e, filterData) {
41079  // console.log('RaceMap: Received filtersUpdated event', filterData);
41080
41081  // Map data is now handled by UnifiedFilterHandlers.updatePageContent
41082  // which extracts data from HTML and calls updateMarkers directly
41083  // console.log('RaceMap: Filter update handled by UnifiedFilterHandlers');
41084});
41085
41086/**
41087 * Initialize on page load
41088 */
41089$(document).ready(function () {
41090  // Initialize on race search pages (running_events, triathlons, etc.)
41091  const isSearchPage = window.location.pathname.includes('/running_events') ||
41092    window.location.pathname.includes('/searches_fast') ||
41093    window.location.pathname.includes('/triathlons') ||
41094    window.location.pathname.includes('/duathlons');
41095
41096  if (isSearchPage) {
41097    // console.log('RaceMap: Page ready, checking for map...');
41098
41099    // Check if map should be visible on page load
41100    const mapContainer = document.getElementById('race-map-container');
41101    if (mapContainer && mapContainer.style.display !== 'none') {
41102      // console.log('RaceMap: Map container visible, initializing...');
41103      window.RaceMap.init();
41104    }
41105  }
41106});
41107/**
41108 * Race Actions - Action-First Authentication System
41109 * 
41110 * This module implements the unified action handler pattern where each action
41111 * works both before authentication (triggers auth modal) and after authentication 
41112 * (executes the intended action immediately).
41113 * 
41114 * Usage: handleRaceAction('actionType', raceData)
41115 * 
41116 * @version 1.0.0
41117 * @author RaceMob Team
41118 */
41119
41120
41121
41122/**
41123 * Main entry point for all race-related actions
41124 * Handles both authenticated and non-authenticated users
41125 * 
41126 * @param {string} actionType - Type of action ('save', 'going', 'review', 'tips', 'discounts')
41127 * @param {Object} raceData - Race data object with id, name, etc.
41128 */
41129
41130function handleRaceAction(actionType, raceData) {
41131
41132  
41133  // Ensure we have valid race data
41134  if (!raceData) {
41135    raceData = getRaceDataForModal();
41136  }
41137  
41138  // Check if user is logged in
41139  if (!window.userLoggedIn) {
41140
41141    // Not logged in: Show auth modal with intended action
41142    showAuthModalForRace(raceData, actionType);
41143    return;
41144  }
41145  
41146
41147  // Logged in: Execute the action immediately
41148  executeRaceAction(actionType, raceData);
41149}
41150
41151/**
41152 * Execute race actions for authenticated users
41153 * This is called both directly (when user is already logged in) 
41154 * and after successful authentication (from page refresh handler)
41155 * 
41156 * @param {string} actionType - Type of action to execute
41157 * @param {Object} raceData - Race data object
41158 */
41159function executeRaceAction(actionType, raceData) {
41160
41161  
41162  const raceId = raceData.id || raceData.raceId || window.raceData?.id || window.raceId;
41163  
41164  switch(actionType) {
41165    case 'save':
41166      executeSaveRaceAction(raceId, raceData);
41167      break;
41168
41169    case 'going':
41170      executeGoingAction(raceId, raceData);
41171      break;
41172
41173    case 'register_wizard':
41174      // Post-OAuth resume of the rebuilt in-wizard registration (race rebuild
41175      // page): reopen at crew with pre-redirect state. raceData here is the
41176      // stored {raceId, wizardState}. Legacy pages without the rebuilt wizard
41177      // fall back to the plain going flow.
41178      if (typeof window.resumeRegistrationWizard === 'function') {
41179        window.resumeRegistrationWizard(raceData);
41180      } else {
41181        executeGoingAction(raceId, raceData);
41182      }
41183      break;
41184
41185    case 'review':
41186      executeReviewAction(raceId, raceData);
41187      break;
41188
41189    case 'tips':
41190      executeTipsAction(raceId, raceData);
41191      break;
41192
41193    case 'discounts':
41194      executeDiscountsAction(raceId, raceData);
41195      break;
41196
41197    case 'login_success':
41198      executeLoginSuccessAction(raceData);
41199      break;
41200
41201    case 'alert':
41202      executeAlertAction(raceId, raceData);
41203      break;
41204
41205    case 'comment':
41206      executeCommentAction(raceId, raceData);
41207      break;
41208
41209    case 'use_community_filter':
41210      executeCommunityFilterAction(raceId, raceData);
41211      break;
41212
41213    default:
41214
41215      showSuccessMessage(`Welcome! You are now logged in and can interact with race features.`);
41216  }
41217}
41218
41219/**
41220 * Execute save race action - opens save confirmation or directly saves
41221 */
41222function executeSaveRaceAction(raceId, raceData) {
41223
41224  
41225  // Try to open save modal if it exists
41226  const saveModalFunctions = [
41227    'openSaveRaceModal',
41228    'showSaveModal', 
41229    'showSaveConfirmationDialog',
41230    'openSaveDialog'
41231  ];
41232  
41233  if (tryCallModalFunction(saveModalFunctions, raceId, raceData)) {
41234    return; // Modal opened successfully
41235  }
41236  
41237  // Fallback: Direct save action
41238  saveRaceToProfile(null, raceId);
41239  showSuccessMessage('Race saved to your profile!');
41240  updateSaveButtonState(raceId);
41241}
41242
41243/**
41244 * Execute "I'm going" action - opens registration modal
41245 */
41246function executeGoingAction(raceId, raceData) {
41247
41248  
41249  // Try to open registration modal functions
41250  const goingModalFunctions = [
41251    'openEnhancedRegistrationModal',
41252    'showRegistrationModal',
41253    'openRegistrationModal',
41254    'showGoingModal',
41255    'openGoingDialog'
41256  ];
41257  
41258  if (tryCallModalFunction(goingModalFunctions, raceId, raceData)) {
41259    return; // Modal opened successfully
41260  }
41261  
41262  // Fallback: Direct attendance marking
41263  markAttendance(null, raceId);
41264  showSuccessMessage('Welcome! You are now registered for this race!');
41265  updateGoingButtonState(raceId);
41266}
41267
41268/**
41269 * Execute review action - opens review modal
41270 */
41271function executeReviewAction(raceId, raceData) {
41272
41273  
41274  const reviewModalFunctions = [
41275    'showReviewModal',
41276    'openReviewModal',
41277    'openReviewForm',
41278    'showReviewForm'
41279  ];
41280  
41281  if (tryCallModalFunction(reviewModalFunctions, raceId, raceData)) {
41282    return; // Modal opened successfully
41283  }
41284  
41285  // Fallback: Show success message
41286  showSuccessMessage('Welcome! You can now write reviews for races you\'ve completed.');
41287}
41288
41289/**
41290 * Execute tips action - opens tips modal
41291 */
41292function executeTipsAction(raceId, raceData) {
41293
41294  
41295  const tipsModalFunctions = [
41296    'showAddTipModal',
41297    'openTipsModal',
41298    'showTipsForm',
41299    'openTipForm'
41300  ];
41301  
41302  if (tryCallModalFunction(tipsModalFunctions, raceId, raceData)) {
41303    return; // Modal opened successfully
41304  }
41305  
41306  // Fallback: Show success message
41307  showSuccessMessage('Welcome! You can now share tips and advice with other runners.');
41308}
41309
41310/**
41311 * Execute discounts action - opens discounts modal
41312 */
41313function executeDiscountsAction(raceId, raceData) {
41314
41315  
41316  const discountModalFunctions = [
41317    'openDiscountModal',
41318    'showDiscountsModal', 
41319    'showDiscountForm',
41320    'openDiscountForm'
41321  ];
41322  
41323  if (tryCallModalFunction(discountModalFunctions, raceId, raceData)) {
41324    return; // Modal opened successfully
41325  }
41326  
41327  // Fallback: Show success message
41328  showSuccessMessage('Welcome! You can now share discount codes and deals with other runners.');
41329}
41330
41331/**
41332 * Execute price alert action - creates price alerts for races
41333 */
41334function executeAlertAction(raceId, raceData) {
41335
41336
41337  const alertModalFunctions = [
41338    'createPriceAlert',
41339    'openPriceAlertModal',
41340    'showPriceAlertModal',
41341    'showAlertForm'
41342  ];
41343
41344  if (tryCallModalFunction(alertModalFunctions, raceId, raceData)) {
41345    return; // Modal or function called successfully
41346  }
41347
41348  // Fallback: Show success message
41349  showSuccessMessage('Welcome! You can now set up price alerts for this race.');
41350}
41351
41352/**
41353 * Execute comment action - enables commenting or redirects to discussion
41354 */
41355function executeCommentAction(raceId, raceData) {
41356
41357
41358  // Check if we have topic data for redirect
41359  if (raceData && raceData.topicData && raceData.topicData.url) {
41360    window.location.href = raceData.topicData.url;
41361    return;
41362  }
41363
41364  const commentModalFunctions = [
41365    'openCommentModal',
41366    'showCommentForm',
41367    'enableCommenting',
41368    'showDiscussionModal'
41369  ];
41370
41371  if (tryCallModalFunction(commentModalFunctions, raceId, raceData)) {
41372    return; // Modal opened successfully
41373  }
41374
41375  // Fallback: Show success message
41376  showSuccessMessage('Welcome! You can now join discussions and comment on races.');
41377}
41378
41379/**
41380 * Execute community filter action - applies friend filters to race search
41381 */
41382function executeCommunityFilterAction(raceId, raceData) {
41383
41384  // Check if we have filter type data
41385  if (raceData && raceData.filterType) {
41386    // Map filterType to actual URL parameters the search controller understands
41387    const currentUrl = new URL(window.location.href);
41388
41389    switch(raceData.filterType) {
41390      case 'friends':
41391        // Enable friends' races filter
41392        currentUrl.searchParams.set('friends_only', 'true');
41393        break;
41394      case 'saved':
41395        // Enable saved races filter
41396        currentUrl.searchParams.set('saved_only', 'true');
41397        break;
41398      default:
41399        // If unknown filter type, just show success message
41400        showSuccessMessage('Welcome! You can now use community filters to see races.');
41401        return;
41402    }
41403
41404    // Redirect to apply the filter
41405    window.location.href = currentUrl.toString();
41406  } else {
41407    showSuccessMessage('Welcome! You can now use community filters to 
41407see races your friends are running.');
41408  }
41409}
41410
41411/**
41412 * Execute login success action - shows welcome toast
41413 */
41414function executeLoginSuccessAction(raceData) {
41415  // Show toast if requested
41416  if (raceData && raceData.showToast) {
41417    if (typeof showSuccessMessage === 'function') {
41418      showSuccessMessage('Welcome! You are now logged in.');
41419    }
41420  }
41421
41422  // Use proper reload to bypass cache and get fresh server-rendered content
41423  // This ensures navigation and all server-side login checks are up-to-date
41424  setTimeout(function() {
41425    window.location.reload();
41426  }, raceData && raceData.showToast ? 1500 : 500);
41427}
41428
41429/**
41430 * Helper function to try calling modal functions by name
41431 * @param {Array} functionNames - Array of possible function names to try
41432 * @param {string} raceId - Race ID to pass to the function
41433 * @param {Object} raceData - Race data to pass to the function
41434 * @returns {boolean} True if a function was called successfully
41435 */
41436function tryCallModalFunction(functionNames, raceId, raceData) {
41437  for (const funcName of functionNames) {
41438    if (typeof window[funcName] === 'function') {
41439
41440      try {
41441        window[funcName](raceId, raceData);
41442        return true;
41443      } catch (error) {
41444
41445        continue; // Try next function
41446      }
41447    }
41448  }
41449  
41450
41451  return false;
41452}
41453
41454/**
41455 * Update save button state after successful save
41456 */
41457function updateSaveButtonState(raceId) {
41458  const saveButtons = document.querySelectorAll('[data-action="save"], .save-button, .btn-save');
41459  saveButtons.forEach(button => {
41460    if (button.dataset.raceId === raceId || !button.dataset.raceId) {
41461      button.textContent = '✓ Saved';
41462      button.classList.add('saved', 'active');
41463      button.disabled = true;
41464    }
41465  });
41466}
41467
41468/**
41469 * Update "I'm going" button state after successful registration
41470 */
41471function updateGoingButtonState(raceId) {
41472  const goingButtons = document.querySelectorAll('[data-action="going"], .going-button, .btn-going');
41473  goingButtons.forEach(button => {
41474    if (button.dataset.raceId === raceId || !button.dataset.raceId) {
41475      button.textContent = '✓ I\'m Going';
41476      button.classList.add('going', 'active');
41477      button.disabled = true;
41478    }
41479  });
41480}
41481
41482/**
41483 * Fallback function to get race data if not provided
41484 * Uses the existing getRaceDataForModal function from race_modern.js
41485 */
41486function getRaceDataForModal() {
41487  // Try to use the existing function first
41488  if (typeof window.getRaceDataForModal === 'function') {
41489    return window.getRaceDataForModal();
41490  }
41491  
41492  // Fallback: Extract basic race data from page
41493  const raceTitle = document.querySelector('h1')?.textContent || 'This Race';
41494  const raceId = window.raceData?.id || window.raceId || null;
41495  
41496  return {
41497    id: raceId,
41498    name: raceTitle,
41499    date: 'Race Day',
41500    location: 'Location TBD'
41501  };
41502}
41503
41504// Export functions to global scope
41505window.handleRaceAction = handleRaceAction;
41506window.executeRaceAction = executeRaceAction;
41507
41508// Also export individual action handlers for direct use if needed
41509window.executeSaveRaceAction = executeSaveRaceAction;
41510window.executeGoingAction = executeGoingAction;
41511window.executeReviewAction = executeReviewAction;
41512window.executeTipsAction = executeTipsAction;
41513window.executeDiscountsAction = executeDiscountsAction;
41514
41515/**
41516 * Universal Friend Inviter Component
41517 * 
41518 * A reusable JavaScript component for inviting friends to various RaceMob activities.
41519 * Supports both existing RaceMob users (via autocomplete search) and external users (via email).
41520 * 
41521 * Usage:
41522 * const inviter = new UniversalFriendInviter({
41523 *   container: '#friend-inviter-container',
41524 *   invitableType: 'RaceOccurence',
41525 *   invitableId: 123,
41526 *   context: 'registration'
41527 * });
41528 */
41529
41530// Prevent duplicate declarations
41531if (typeof UniversalFriendInviter === 'undefined') {
41532  
41533class UniversalFriendInviter {
41534  constructor(options) {
41535    this.options = {
41536      container: options.container,
41537      invitableType: options.invitableType,
41538      invitableId: options.invitableId,
41539      context: options.context || 'registration',
41540      placeholder: options.placeholder || 'Search friends or enter email addresses...',
41541      maxInvitations: options.maxInvitations || 20,
41542      debounceDelay: options.debounceDelay || 300,
41543      ...options
41544    };
41545    
41546    this.selectedFriends = [];
41547    this.selectedEmails = [];
41548    this.searchTimeout = null;
41549    this.isSearching = false;
41550    
41551    this.init();
41552  }
41553  
41554  init() {
41555    this.container = document.querySelector(this.options.container);
41556    if (!this.container) {
41557
41558      return;
41559    }
41560    
41561    this.render();
41562    this.attachEventListeners();
41563  }
41564  
41565  /**
41566   * Show error message using notification system
41567   * @param {string} message - Error message
41568   */
41569  showError(message) {
41570    if (window.UIHelpers && window.UIHelpers.showNotification) {
41571      window.UIHelpers.showNotification('error', 'Error', message);
41572    } else if (window.showNotification) {
41573      window.showNotification('error', 'Error', message);
41574    } else {
41575      console.error('Error:', message);
41576      alert(message); // Keep as fallback
41577    }
41578  }
41579  
41580  render() {
41581    this.container.innerHTML = `
41582      <div class="friend-inviter">
41583        <div class="friend-search-section">
41584          <div class="search-input-container">
41585            <input type="text" 
41586                   class="friend-search-input" 
41587                   placeholder="${this.options.placeholder}"
41588                   autocomplete="off">
41589            <div class="search-dropdown hidden">
41590              <div class="search-results"></div>
41591              <div class="search-loading hidden">
41592                <span class="loading-spinner"></span>
41593                <span>Searching...</span>
41594              </div>
41595              <div class="search-empty hidden">
41596                <span>No users found</span>
41597              </div>
41598            </div>
41599          </div>
41600        </div>
41601        
41602        <div class="selected-friends-section">
41603          <div class="selected-friends-container">
41604            <!-- Selected friend chips will appear here -->
41605          </div>
41606        </div>
41607        
41608        <div class="invitation-note-section">
41609          <textarea class="invitation-note-textarea personal-note" 
41610                    placeholder="Add a personal message... 'Let's do this together! Meeting at the start line at 7am 🏃‍♀️'"
41611                    maxlength="500"
41612                    name="personal_message"></textarea>
41613        </div>
41614      </div>
41615    `;
41616    
41617    this.searchInput = this.container.querySelector('.friend-search-input');
41618    this.searchDropdown = this.container.querySelector('.search-dropdown');
41619    this.searchResults = this.container.querySelector('.search-results');
41620    this.searchLoading = this.container.querySelector('.search-loading');
41621    this.searchEmpty = this.container.querySelector('.search-empty');
41622    this.selectedContainer = this.container.querySelector('.selected-friends-container');
41623    this.noteTextarea = this.container.querySelector('.invitation-note-textarea');
41624  }
41625  
41626  attachEventListeners() {
41627    // Search input events
41628    this.searchInput.addEventListener('input', (e) => {
41629      this.handleSearchInput(e.target.value);
41630    });
41631    
41632    this.searchInput.addEventListener('keydown', (e) => {
41633      this.handleSearchKeydown(e);
41634    });
41635    
41636    this.searchInput.addEventListener('focus', () => {
41637      const value = this.searchInput.value.trim();
41638      if (value.length > 0) {
41639        // Re-trigger search on focus
41640        this.handleSearchInput(value);
41641      }
41642    });
41643    
41644    // Note textarea event listener (if needed)
41645    // Currently no character counting as per prototype
41646    
41647    // Click outside to close dropdown
41648    // One document listener for every instance (modals build a new inviter per open); it acts on
41649    // the newest one, so reopening never stacks listeners.
41650    UniversalFriendInviter.active = this;
41651    if (!UniversalFriendInviter.docClickBound) {
41652      UniversalFriendInviter.docClickBound = true;
41653      document.addEventListener('click', (e) => {
41654        const live = UniversalFriendInviter.active;
41655        if (live && !live.container.contains(e.target)) {
41656          live.hideDropdown();
41657        }
41658      });
41659    }
41660  }
41661  
41662  handleSearchInput(value) {
41663    clearTimeout(this.searchTimeout);
41664    
41665    // Check for comma-separated emails
41666    if (value.includes(',')) {
41667      const parts = value.split(',');
41668      const lastPart = parts[parts.length - 1].trim();
41669      
41670      // Process all complete emails before the last comma
41671      for (let i = 0; i < parts.length - 1; i++) {
41672        const email = parts[i].trim();
41673        if (this.isValidEmail(email)) {
41674          this.addEmail(email);
41675        }
41676      }
41677      
41678      // Update input with just the last part
41679      this.searchInput.value = lastPart;
41680      value = lastPart;
41681    }
41682    
41683    if (value.trim().length === 0) {
41684      this.hideDropdown();
41685      return;
41686    }
41687    
41688    // Check if it looks like an email
41689    if (this.isValidEmail(value)) {
41690      this.showEmailOption(value);
41691      return;
41692    }
41693    
41694    // Debounce user search
41695    this.searchTimeout = setTimeout(() => {
41696      this.searchUsers(value);
41697    }, this.options.debounceDelay);
41698  }
41699  
41700  handleSearchKeydown(e) {
41701    if (e.key === 'Enter' || e.key === ' ' || e.key === ',') {
41702      const value = e.target.value.trim();
41703      
41704      if (this.isValidEmail(value)) {
41705        e.preventDefault();
41706        this.addEmail(value);
41707        this.clearSearch();
41708      } else if (e.key === 'Enter') {
41709        e.preventDefault();
41710      }
41711    }
41712    
41713    if (e.key === 'Escape') {
41714      this.hideDropdown();
41715      this.clearSearch();
41716    }
41717  }
41718  
41719  async searchUsers(query) {
41720    if (this.isSearching) return;
41721    
41722    this.isSearching = true;
41723    this.showLoading();
41724    
41725    try {
41726      const response = await fetch(`/races/autocomplete_user_for_invitation?term=${encodeURIComponent(query)}`, {
41727        headers: {
41728          'X-Requested-With': 'XMLHttpRequest'
41729        }
41730      });
41731      
41732      if (!response.ok) {
41733        throw new Error(`HTTP ${response.status}`);
41734      }
41735      
41736      const users = await response.json();
41737      this.displaySearchResults(users);
41738      
41739    } catch (error) {
41740
41741      this.showEmpty();
41742    } finally {
41743      this.isSearching = false;
41744      this.hideLoading();
41745    }
41746  }
41747  
41748  displaySearchResults(users) {
41749    if (users.length === 0) {
41750      // Check if the search term looks like it might be an email
41751      const searchTerm = this.searchInput.value.trim();
41752      if (searchTerm.includes('@')) {
41753        this.showEmailHint(searchTerm);
41754      } else {
41755        this.showEmpty();
41756      }
41757      return;
41758    }
41759    
41760    this.searchResults.innerHTML = users.map(user => `
41761      <div class="search-result-item" data-type="user" data-id="${user.id}">
41762        ${user.avatar_url ? 
41763          `<img src="${user.avatar_url}" alt="${user.display_name}" class="user-avatar">` :
41764          `<div class="user-avatar user-avatar-placeholder">${user.display_name.charAt(0).toUpperCase()}</div>`
41765        }
41766        <div class="user-info">
41767          <div class="user-name">${user.display_name}</div>
41768          <div class="user-username">@${user.username}</div>
41769        </div>
41770      </div>
41771    `).join('');
41772    
41773    // Add click handlers
41774    this.searchResults.querySelectorAll('.search-result-item').forEach(item => {
41775      item.addEventListener('click', () => {
41776        const userId = item.dataset.id;
41777        const user = users.find(u => u.id.toString() === userId);
41778        this.addUser(user);
41779        this.clearSearch();
41780      });
41781    });
41782    
41783    // Show the results and dropdown
41784    this.searchResults.classList.remove('hidden');
41785    this.searchEmpty.classList.add('hidden');
41786    this.searchLoading.classList.add('hidden');
41787    this.showDropdown();
41788  }
41789  
41790  showEmailOption(email) {
41791    this.searchResults.innerHTML = `
41792      <div class="search-result-item email-option" data-type="email" data-email="${email}">
41793        <div class="email-icon">📧</div>
41794        <div class="email-info">
41795          <div class="email-address">${email}</div>
41796          <div class="email-note">Invite via email</div>
41797        </div>
41798      </div>
41799    `;
41800    
41801    this.searchResults.querySelector('.email-option').addEventListener('click', () => {
41802      this.addEmail(email);
41803      this.clearSearch();
41804    });
41805    
41806    this.showDropdown();
41807  }
41808  
41809  addUser(user) {
41810    // Check if already added
41811    if (this.selectedFriends.find(f => f.id === user.id)) {
41812      return;
41813    }
41814    
41815    // Check total limit
41816    if (this.getTotalSelected() >= this.options.maxInvitations) {
41817      if (window.UIHelpers && window.UIHelpers.showNotification) {
41818        window.UIHelpers.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`);
41819      } else if (window.showNotification) {
41820        window.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`);
41821      } else {
41822        this.showError(`Cannot invite more than ${this.options.maxInvitations} people at once.`);
41823      }
41824      return;
41825    }
41826    
41827    this.selectedFriends.push(user);
41828    this.renderSelectedItems();
41829    this.updateFormInputs();
41830  }
41831  
41832  addEmail(email) {
41833    email = email.toLowerCase().trim();
41834    
41835    // Check if already added
41836    if (this.selectedEmails.includes(email)) {
41837      return;
41838    }
41839    
41840    // Check if email belongs to selected user
41841    if (this.selectedFriends.find(f => f.email && f.email.toLowerCase() === email)) {
41842      return;
41843    }
41844    
41845    // Check total limit
41846    if (this.getTotalSelected() >= this.options.maxInvitations) {
41847      if (window.UIHelpers && window.UIHelpers.showNotification) {
41848        window.UIHelpers.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`);
41849      } else if (window.showNotification) {
41850        window.showNotification('warning', 'Invitation Limit', `Cannot invite more than ${this.options.maxInvitations} people at once.`);
41851      } else {
41852        this.showError(`Cannot invite more than ${this.options.maxInvitations} people at once.`);
41853      }
41854      return;
41855    }
41856    
41857    if (!this.isValidEmail(email)) {
41858      if (window.UIHelpers && window.UIHelpers.showNotification) {
41859        window.UIHelpers.showNotification('error', 'Invalid Email', 'Please enter a valid email address.');
41860      } else if (window.showNotification) {
41861        window.showNotification('error', 'Invalid Email', 'Please enter a valid email address.');
41862      } else {
41863        this.showError('Please enter a valid email address.');
41864      }
41865      return;
41866    }
41867    
41868    this.selectedEmails.push(email);
41869    this.renderSelectedItems();
41870    this.updateFormInputs();
41871  }
41872  
41873  removeUser(userId) {
41874    this.selectedFriends = this.selectedFriends.filter(f => f.id !== userId);
41875    this.renderSelectedItems();
41876    this.updateFormInputs();
41877  }
41878  
41879  removeEmail(email) {
41880    this.selectedEmails = this.selectedEmails.filter(e => e !== email);
41881    this.renderSelectedItems();
41882    this.updateFormInputs();
41883  }
41884  
41885  renderSelectedItems() {
41886    const userChips = this.selectedFriends.map(user => `
41887      <div class="friend-chip" data-type="user" data-id="${user.id}">
41888        ${user.avatar_url ? 
41889          `<img src="${user.avatar_url}" alt="${user.display_name}" class="friend-avatar">` :
41890          `<div class="friend-avatar friend-avatar-placeholder">${user.display_name.charAt(0).toUpperCase()}</div>`
41891        }
41892        <span class="friend-name">${user.display_name} @${user.username}</span>
41893        <button type="button" class="remove-btn" onclick="event.preventDefault()">×</button>
41894      </div>
41895    `).join('');
41896    
41897    const emailChips = this.selectedEmails.map(email => `
41898      <div class="friend-chip" data-type="email" data-email="${email}">
41899        <span class="email-icon">📧</span>
41900        <span class="friend-name">${email}</span>
41901        <button type="button" class="remove-btn" onclick="event.preventDefault()">×</button>
41902      </div>
41903    `).join('');
41904    
41905    this.selectedContainer.innerHTML = userChips + emailChips;
41906    
41907    // Add remove handlers
41908    this.selectedContainer.querySelectorAll('.remove-btn').forEach(button => {
41909      button.addEventListener('click', (e) => {
41910        e.preventDefault();
41911        const chip = button.closest('.friend-chip');
41912        
41913        if (chip.dataset.type === 'user') {
41914          this.removeUser(parseInt(chip.dataset.id));
41915        } else {
41916          this.removeEmail(chip.dataset.email);
41917        }
41918      });
41919    });
41920  }
41921  
41922  updateFormInputs() {
41923    // Remove existing hidden inputs
41924    this.container.querySelectorAll('input[name^="invitation"]').forEach(input => input.remove());
41925    
41926    // Add hidden inputs for selected users
41927    this.selectedFriends.forEach(user => {
41928      const input = document.createElement('input');
41929      input.type = 'hidden';
41930      input.name = 'invitation[invited_users][]';
41931      input.value = user.id;
41932      this.container.appendChild(input);
41933    });
41934    
41935    // Add hidden inputs for selected emails
41936    this.selectedEmails.forEach(email => {
41937      const input = document.createElement('input');
41938      input.type = 'hidden';
41939      input.name = 'invitation[invited_emails][]';
41940      input.value = email;
41941      this.container.appendChild(input);
41942    });
41943    
41944    // Add other invitation data
41945    const contextInput = document.createElement('input');
41946    contextInput.type = 'hidden';
41947    contextInput.name = 'invitation[context]';
41948    contextInput.value = this.options.context;
41949    this.container.appendChild(contextInput);
41950    
41951    if (this.options.invitableType && this.options.invitableId) {
41952      const typeInput = document.createElement('input');
41953      typeInput.type = 'hidden';
41954      typeInput.name = 'invitation[invitable_type]';
41955      typeInput.value = this.options.invitableType;
41956      this.container.appendChild(typeInput);
41957      
41958      const idInput = document.createElement('input');
41959      idInput.type = 'hidden';
41960      idInput.name = 'invitation[invitable_id]';
41961      idInput.value = this.options.invitableId;
41962      this.container.appendChild(idInput);
41963    }
41964    
41965    // Add note input
41966    const noteInput = document.createElement('input');
41967    noteInput.type = 'hidden';
41968    noteInput.name = 'invitation[invitation_note]';
41969    noteInput.value = this.noteTextarea.value;
41970    this.container.appendChild(noteInput);
41971  }
41972  
41973  
41974  showDropdown() {
41975    this.searchDropdown.classList.remove('hidden');
41976  }
41977  
41978  hideDropdown() {
41979    this.searchDropdown.classList.add('hidden');
41980  }
41981  
41982  showLoading() {
41983    this.searchLoading.classList.remove('hidden');
41984    this.searchResults.classList.add('hidden');
41985    this.searchEmpty.classList.add('hidden');
41986  }
41987  
41988  hideLoading() {
41989    this.searchLoading.classList.add('hidden');
41990  }
41991  
41992  showEmpty() {
41993    this.searchEmpty.classList.remove('hidden');
41994    this.searchResults.classList.add('hidden');
41995    this.searchLoading.classList.add('hidden');
41996  }
41997
41998  showEmailHint(searchTerm) {
41999    // Show a helpful message for email addresses
42000    this.searchResults.innerHTML = `
42001      <div class="search-email-hint">
42002        <div class="email-hint-icon">✉️</div>
42003        <div class="email-hint-text">
42004          <div class="email-hint-title">Invite by email</div>
42005          <div class="email-hint-subtitle">Press Enter, Space, or comma to add email</div>
42006        </div>
42007      </div>
42008    `;
42009    this.searchResults.classList.remove('hidden');
42010    this.searchEmpty.classList.add('hidden');
42011    this.searchLoading.classList.add('hidden');
42012    this.showDropdown();
42013  }
42014  
42015  clearSearch() {
42016    this.searchInput.value = '';
42017    this.hideDropdown();
42018  }
42019  
42020  getTotalSelected() {
42021    return this.selectedFriends.length + this.selectedEmails.length;
42022  }
42023  
42024  isValidEmail(email) {
42025    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
42026    return emailRegex.test(email);
42027  }
42028  
42029  // Public API methods
42030  getSelectedData() {
42031    this.updateFormInputs();
42032    return {
42033      invited_users: this.selectedFriends.map(f => f.id),
42034      invited_emails: this.selectedEmails,
42035      invitation_note: this.noteTextarea.value,
42036      context: this.options.context,
42037      invitable_type: this.options.invitableType,
42038      invitable_id: this.options.invitableId
42039    };
42040  }
42041  
42042  reset() {
42043    this.selectedFriends = [];
42044    this.selectedEmails = [];
42045    this.renderSelectedItems();
42046    this.updateFormInputs();
42047    this.clearSearch();
42048    this.noteTextarea.value = '';
42049  }
42050  
42051  hasSelections() {
42052    return this.getTotalSelected() > 0;
42053  }
42054}
42055
42056// Export for use in other modules
42057window.UniversalFriendInviter = UniversalFriendInviter;
42058
42059} // End of UniversalFriendInviter class definition check
42060;
42061// Enhanced Registration Modal JavaScript
42062
42063function escapeHtml(str) {
42064  if (!str) return '';
42065  var div = document.createElement('div');
42066  div.textContent = str;
42067  return div.innerHTML;
42068}
42069
42070/**
42071 * Show error message using notification system
42072 * @param {string} message - Error message
42073 */
42074function showRegistrationError(message) {
42075  // If on auth step, show inline error instead of toast
42076  if (authStepActive && currentStep === 3) {
42077    showAuthStepError(message);
42078    return;
42079  }
42080
42081  // Otherwise use toast notifications
42082  if (window.UIHelpers && window.UIHelpers.showNotification) {
42083    window.UIHelpers.showNotification('error', 'Registration Error', message);
42084  } else if (window.showNotification) {
42085    window.showNotification('error', 'Registration Error', message);
42086  } else {
42087    console.error('Registration Error:', message);
42088    alert(message); // Keep as fallback
42089  }
42090}
42091
42092function showAuthStepError(message) {
42093  // Remove any existing error message
42094  const existingError = document.querySelector('.auth-step-error');
42095  if (existingError) {
42096    existingError.remove();
42097  }
42098
42099  // Find the active form
42100  const activeForm = document.querySelector('.auth-form-container[style*="display: block"]') ||
42101                     document.querySelector('.signup-form');
42102
42103  if (!activeForm) return;
42104
42105  // Create error element
42106  const errorDiv = document.createElement('div');
42107  errorDiv.className = 'auth-step-error';
42108  errorDiv.style.cssText = `
42109    background: #FEE2E2;
42110    border: 1px solid #EF4444;
42111    border-radius: 8px;
42112    padding: 12px 16px;
42113    margin-top: 16px;
42114    color: #991B1B;
42115    font-size: 14px;
42116    line-height: 1.5;
42117    display: flex;
42118    align-items: start;
42119    gap: 8px;
42120  `;
42121
42122  errorDiv.innerHTML = `
42123    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink: 0; margin-top: 2px;">
42124      <circle cx="10" cy="10" r="8"></circle>
42125      <line x1="10" y1="6" x2="10" y2="10"></line>
42126      <line x1="10" y1="14" x2="10.01" y2="14"></line>
42127    </svg>
42128    <span>${message}</span>
42129  `;
42130
42131  // Insert error after the last DIRECT-CHILD form-group of the active form
42132  // container. Using `:scope >` avoids matching form-groups nested inside
42133  // hidden subforms (eg. #reg-modal-forgot-password), which previously caused
42134  // the error div to render inside a `display: none` parent — invisible.
42135  const formGroups = activeForm.querySelectorAll(':scope > .form-group');
42136  const lastFormGroup = formGroups[formGroups.length - 1];
42137  if (lastFormGroup) {
42138    lastFormGroup.parentNode.insertBefore(errorDiv, lastFormGroup.nextSibling);
42139  } else {
42140    activeForm.appendChild(errorDiv);
42141  }
42142
42143  // Auto-remove after 5 seconds
42144  setTimeout(() => {
42145    if (errorDiv.parentNode) {
42146      errorDiv.style.transition = 'opacity 0.3s ease-out';
42147      errorDiv.style.opacity = '0';
42148      setTimeout(() => errorDiv.remove(), 300);
42149    }
42150  }, 5000);
42151}
42152
42153function clearAuthStepError() {
42154  const existingError = document.querySelector('.auth-step-error');
42155  if (existingError) {
42156    existingError.remove();
42157  }
42158}
42159
42160if (typeof currentStep === 'undefined') {
42161  var currentStep = 1;
42162}
42163if (typeof totalSteps === 'undefined') {
42164  var totalSteps = 5;  // Base steps (without auth)
42165}
42166if (typeof authStepActive === 'undefined') {
42167  var authStepActive = false;  // Track if auth step is inserted
42168}
42169if (typeof selectedParticipationType === 'undefined') {
42170  var selectedParticipationType = 'participant';
42171}
42172if (typeof friendInviter === 'undefined') {
42173  var friendInviter = null;
42174}
42175if (typeof isInEditMode === 'undefined') {
42176  var isInEditMode = false;
42177}
42178
42179// Resend the verification email from the crew-step overlay (shared by the
42180// server-rendered ERB overlay and the JS-created one). Swaps FA icon classes
42181// rather than textContent so the glyph survives.
42182window.sendVerificationEmailFromCrewOverlay = function(btn) {
42183  btn.disabled = true;
42184  btn.textContent = 'Sending...';
42185
42186  var icon = document.getElementById('crew-overlay-icon');
42187  var title = document.getElementById('crew-overlay-title');
42188  var message = document.getElementById('crew-overlay-message');
42189  var userEmail = document.querySelector('[data-user-email]')?.getAttribute('data-user-email');
42190
42191  fetch('/user/send_verification_email', {
42192    method: 'POST',
42193    headers: {
42194      'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content,
42195      'Content-Type': 'application/json',
42196      'Accept': 'application/json'
42197    }
42198  })
42199  .then(function(response) { return response.json(); })
42200  .then(function(data) {
42201    if (data.success) {
42202      if (icon) icon.className = 'fa-solid fa-circle-check';
42203      if (title) title.textContent = 'Verification email sent';
42204      if (message) message.textContent = userEmail
42205        ? `Check ${userEmail} and click the link to enable this.`
42206        : 'Check your inbox and click the link to enable this.';
42207      btn.textContent = 'Email sent';
42208    } else {
42209      throw new Error(data.error || 'Failed to send email');
42210    }
42211  })
42212  .catch(function(error) {
42213    if (icon) icon.className = 'fa-solid fa-triangle-exclamation';
42214    if (title) title.textContent = 'Could not send email';
42215    if (message) message.textContent = error.message || 'Please try again or contact support.';
42216    btn.textContent = 'Try again';
42217    btn.disabled = false;
42218  });
42219};
42220
42221// Open the enhanced registration modal
42222function openEnhancedRegistrationModal(isEditMode = false, skipReset = false) {
42223  const modal = document.getElementById('enhanced-registration-modal');
42224  if (!modal) {
42225
42226    return;
42227  }
42228
42229  // Set global edit mode state
42230  isInEditMode = isEditMode;
42231
42232  // Show the modal — rm-modal chassis owns overlay/scroll-lock/focus/ESC.
42233  if (window.RmModal) window.RmModal.open('enhanced-registration');
42234
42235  // Small delay to ensure DOM is ready
42236  setTimeout(() => {
42237    // Only reset modal if not in edit mode AND not already reset
42238    if (!isEditMode && !skipReset) {
42239      resetModal();
42240    } else if (isEditMode) {
42241      // In edit mode, update header and ensure we're on step 1
42242      updateModalHeaderForEditMode();
42243      currentStep = 1;
42244      updateStepDisplay();
42245      updateProgressIndicator();
42246      updateButtons();
42247    }
42248
42249    initializeFriendInviter();
42250    setupAuthStepKeyboardHandlers();
42251    
42252    // If in edit mode, pre-populate form and add delete button
42253    if (isEditMode) {
42254
42255
42256
42257      
42258      if (window.userRegistrationData) {
42259        populateRegistrationForm(window.userRegistrationData);
42260        showCantMakeItButton();
42261        updateSubmitButtonForEditMode();
42262      } else {
42263
42264        // Try to fetch the registration data dynamically
42265        fetchUserRegistrationData();
42266      }
42267    }
42268  }, 10);
42269}
42270
42271
42272// Update modal header for edit mode
42273function updateModalHeaderForEditMode() {
42274  const titleElement = document.querySelector('.enhanced-modal-title');
42275  const subtitleElement = document.querySelector('.modal-subtitle');
42276  
42277  if (titleElement && subtitleElement) {
42278    // Get race name from current title (remove "Join ", "! 🎉", and any existing "Update Your Plans for")
42279    const currentTitle = titleElement.textContent;
42280    const raceName = currentTitle
42281      .replace(/^Update Your Plans for\s+/, '')
42282      .replace(/^Join\s+/, '')
42283      .replace(/!\s*🎉\s*$/, '');
42284    
42285    titleElement.textContent = `Update your plans for ${raceName}`;
42286    subtitleElement.textContent = 'Change any details about your registration';
42287    
42288    // Store the clean race name for future use
42289    titleElement.setAttribute('data-race-name', raceName);
42290    
42291
42292  }
42293}
42294
42295// Reset modal header to registration mode
42296function resetModalHeaderForRegistration() {
42297  const titleElement = document.querySelector('.enhanced-modal-title');
42298  const subtitleElement = document.querySelector('.modal-subtitle');
42299  
42300  if (titleElement && subtitleElement) {
42301    // Get race name from current title
42302    const currentTitle = titleElement.textContent;
42303    const raceName = currentTitle.replace(/^Update Your Plans for\s+/, '');
42304    
42305    titleElement.textContent = raceName.startsWith('Join ') ? raceName : `Join ${raceName}`;
42306    subtitleElement.textContent = "Race day is better with a crew. Two minutes, tell yours you're in.";
42307    
42308
42309  }
42310}
42311
42312// Fetch user registration data dynamically if not available
42313function fetchUserRegistrationData() {
42314  if (!window.raceId) {
42315
42316    return;
42317  }
42318  
42319
42320  
42321  fetch(`/races/${window.raceId}/user_registration`, {
42322    method: 'GET',
42323    headers: {
42324      'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content,
42325      'Content-Type': 'application/json',
42326      'Accept': 'application/json'
42327    }
42328  })
42329  .then(response => response.json())
42330  .then(data => {
42331    if (data.registration) {
42332
42333      window.userRegistrationData = data.registration;
42334      populateRegistrationForm(data.registration);
42335      addDeleteButton();
42336    } else {
42337
42338    }
42339  })
42340  .catch(error => {
42341
42342  });
42343}
42344
42345// Initialize the Universal Friend Inviter component
42346function initializeFriendInviter() {
42347  if (typeof UniversalFriendInviter === 'undefined') {
42348
42349    return;
42350  }
42351
42352  // Check if user's email is verified
42353  const modal = document.getElementById('enhanced-registration-modal');
42354  const isVerified = modal && modal.getAttribute('data-user-verified') === 'true';
42355
42356  // Don't initialize friend inviter for unverified users
42357  if (!isVerified) {
42358    console.log('Skipping friend inviter initialization - user email not verified');
42359    return;
42360  }
42361
42362  // Get race ID from window.raceId (set when modal opens)
42363  // DO NOT use document.querySelector('[data-race-id]') - it will grab the first card on search pages
42364  const raceId = window.raceId;
42365
42366  if (!raceId) {
42367    console.warn('Friend inviter: No race ID available');
42368    return;
42369  }
42370
42371  // Initialize the friend inviter component
42372  friendInviter = new UniversalFriendInviter({
42373    container: '#enhanced-registration-friend-inviter',
42374    invitableType: 'RaceOccurence',
42375    invitableId: parseInt(raceId),
42376    context: 'registration',
42377    placeholder: 'Search RaceMob friends or enter email...',
42378    maxInvitations: 20
42379  });
42380
42381  // Update the RaceMobbers going section
42382  updateRaceMobbersGoingSection();
42383}
42384
42385// Close the enhanced registration modal
42386function closeEnhancedRegistrationModal() {
42387  var modal = document.getElementById('enhanced-registration-modal');
42388  if (!modal) return; // Guard clause - modal may not exist on page
42389  if (window.RmModal) window.RmModal.close(modal);
42390
42391  // Phase 8: Redirect to team hub if user selected a primary team during registration
42392  // This drives engagement with team content immediately after registration
42393  if (window.selectedPrimaryTeamId && window.raceId) {
42394    const teamHubUrl = `/races/${window.raceId}/team/${window.selectedPrimaryTeamId}/overview`;
42395    window.selectedPrimaryTeamId = null; // Clear to prevent re-redirect
42396    window.location.href = teamHubUrl;
42397    return;
42398  }
42399
42400  resetModal();
42401
42402  // Reload page if user signed up/in during this session
42403  // This ensures all auth-dependent content refreshes properly
42404  if (window.needsPageRefreshOnModalClose) {
42405    window.needsPageRefreshOnModalClose = false;
42406    window.location.reload();
42407  }
42408}
42409
42410// Reset modal to initial state
42411function resetModal() {
42412  currentStep = 1;
42413  selectedParticipationType = 'participant';
42414  isInEditMode = false; // Reset edit mode
42415  updateStepDisplay();
42416  updateProgressIndicator();
42417  updateButtons();
42418  
42419  // Reset modal header to registration mode
42420  resetModalHeaderForRegistration();
42421  
42422  // Reset form
42423  document.getElementById('enhanced-registration-form').reset();
42424  document.querySelector('input[name="participation_type"][value="participant"]').checked = true;
42425  
42426  // Remove delete button if it exists
42427  removeDeleteButton();
42428  
42429  // Hide "Can't Make it" button
42430  hideCantMakeItButton();
42431  
42432  // Reset submit button to registration mode
42433  updateSubmitButtonForRegistrationMode();
42434  
42435  // Clear registration data
42436  window.userRegistrationData = null;
42437
42438  // Clear cached form values that persist across modal opens
42439  window.selectedDistanceNames = null;
42440
42441  // Reset friend inviter
42442  if (friendInviter) {
42443    friendInviter.reset();
42444  }
42445}
42446
42447// Populate form with existing registration data
42448function populateRegistrationForm(registrationData) {
42449  if (!registrationData) return;
42450  
42451  // Set participation type from primary registration
42452  if (registrationData.participation_type) {
42453    const participationRadio = document.querySelector(`input[name="participation_type"][value="${registrationData.participation_type}"]`);
42454    if (participationRadio) {
42455      participationRadio.checked = true;
42456      selectedParticipationType = registrationData.participation_type;
42457    }
42458  }
42459  
42460  // Set ALL distances from all registrations
42461  // Try distance_ids array first (from modal_data endpoint)
42462  if (registrationData.distance_ids && Array.isArray(registrationData.distance_ids)) {
42463    const distanceNames = [];
42464    registrationData.distance_ids.forEach(distanceId => {
42465      const distanceCheckbox = document.querySelector(`input[name="distance_ids[]"][value="${distanceId}"]`);
42466      if (distanceCheckbox) {
42467        distanceCheckbox.checked = true;
42468        // Also grab the name for summary
42469        const label = distanceCheckbox.closest('label');
42470        const nameElement = label ? label.querySelector('.distance-name') : null;
42471        if (nameElement) {
42472          distanceNames.push(nameElement.textContent.trim());
42473        }
42474      }
42475    });
42476    // Store for summary
42477    if (distanceNames.length > 0) {
42478      window.selectedDistanceNames = distanceNames.join(', ');
42479    }
42480  } else if (window.userRegistrations && window.userRegistrations.length > 0) {
42481    // Fallback to userRegistrations array
42482    window.userRegistrations.forEach(reg => {
42483      if (reg.race_distance_id) {
42484        const distanceCheckbox = document.querySelector(`input[name="distance_ids[]"][value="${reg.race_distance_id}"]`);
42485        if (distanceCheckbox) {
42486          distanceCheckbox.checked = true;
42487        }
42488      }
42489    });
42490  } else if (registrationData.race_distance_id) {
42491    // Fallback to single registration
42492    const distanceCheckbox = document.querySelector(`input[name="distance_ids[]"][value="${registrationData.race_distance_id}"]`);
42493    if (distanceCheckbox) {
42494      distanceCheckbox.checked = true;
42495    }
42496  }
42497  
42498  // Set goal effort
42499  if (registrationData.goal_effort) {
42500    const effortRadio = document.querySelector(`input[name="goal_effort"][value="${registrationData.goal_effort}"]`);
42501    if (effortRadio) {
42502      effortRadio.checked = true;
42503    }
42504  }
42505  
42506  // Set goal pace
42507  if (registrationData.goal_pace_value) {
42508    const paceInput = document.querySelector('input[name="goal_pace_value"]');
42509    if (paceInput) {
42510      paceInput.value = registrationData.goal_pace_value;
42511    }
42512    const paceUnit = document.querySelector('select[name="goal_pace_unit"]');
42513    if (paceUnit && registrationData.goal_pace_unit) {
42514      paceUnit.value = registrationData.goal_pace_unit;
42515    }
42516  }
42517  
42518  // Set notes
42519  if (registrationData.notes) {
42520    const notesTextarea = document.querySelector('textarea[name="notes"]');
42521    if (notesTextarea) {
42522      notesTextarea.value = registrationData.notes;
42523    }
42524  }
42525  
42526  // Set public visibility
42527  const publicCheckbox = document.querySelector('input[name="is_public"]');
42528  if (publicCheckbox) {
42529    publicCheckbox.checked = registrationData.is_public !== false;
42530  }
42531  
42532  // Update buttons and UI after populating form
42533  updateButtons();
42534  updateProgressIndicator();
42535}
42536
42537// Add delete button to the first step
42538function addDeleteButton() {
42539  const firstStepContent = document.querySelector('.step-content[data-step="1"]');
42540  if (!firstStepContent) return;
42541  
42542  // Check if delete button already exists
42543  if (firstStepContent.querySelector('.delete-registration-btn')) return;
42544  
42545  // Create delete button container
42546  const deleteContainer = document.createElement('div');
42547  deleteContainer.style.cssText = 'margin-top: 24px; padding-top: 24px; border-top: 1px solid #E5E7EB; text-align: center;';
42548  
42549  const deleteButton = document.createElement('button');
42550  deleteButton.type = 'button';
42551  deleteButton.className = 'delete-registration-btn';
42552  deleteButton.style.cssText = 'color: #DC2626; font-weight: 500; padding: 8px 16px; border: 1px solid #DC2626; border-radius: 8px; background: white; cursor: pointer; transition: all 200ms ease-out;';
42553  deleteButton.innerHTML = '<i class="fa-solid fa-trash"></i> Remove registration';
42554  deleteButton.onclick = confirmDeleteRegistration;
42555  
42556  deleteButton.onmouseover = function() {
42557    this.style.background = '#FEF2F2';
42558    this.style.borderColor = '#DC2626';
42559  };
42560  
42561  deleteButton.onmouseout = function() {
42562    this.style.background = 'white';
42563    this.style.borderColor = '#DC2626';
42564  };
42565  
42566  deleteContainer.appendChild(deleteButton);
42567  firstStepContent.appendChild(deleteContainer);
42568}
42569
42570// Remove delete button from the first step
42571function removeDeleteButton() {
42572  const deleteButton = document.querySelector('.delete-registration-btn');
42573  if (deleteButton) {
42574    const deleteContainer = deleteButton.parentElement;
42575    if (deleteContainer) {
42576      deleteContainer.remove();
42577    }
42578  }
42579}
42580
42581// Confirm delete registration (show custom modal)
42582function confirmDeleteRegistration() {
42583
42584
42585  
42586  if (!window.raceId) {
42587
42588    if (window.UIHelpers && window.UIHelpers.showNotification) {
42589      window.UIHelpers.showNotification('error', 'Error', 'Race ID not available. Please refresh the page and try again.');
42590    } else if (window.showNotification) {
42591      window.showNotification('error', 'Error', 'Race ID not available. Please refresh the page and try again.');
42592    } else {
42593      showRegistrationError('Error: Race ID not available. Please refresh the page and try again.');
42594    }
42595    return;
42596  }
42597  
42598  // Show the custom deletion confirmation modal
42599  showDeletionConfirmationModal();
42600}
42601
42602// Show deletion confirmation modal
42603function showDeletionConfirmationModal() {
42604  const modal = document.getElementById('deletion-confirmation-modal');
42605  const raceNameSpan = document.getElementById('deletion-race-name');
42606  
42607  if (modal && raceNameSpan) {
42608    // Get race name from the main modal title
42609    const mainTitle = document.querySelector('.enhanced-modal-title');
42610    if (mainTitle) {
42611      const titleText = mainTitle.textContent;
42612      // Extract race name from either "Join [Race Name]! 🎉" or "Update Your Plans for [Race Name]"
42613      let raceName = titleText.replace(/^(Join\s+|Update Your Plans for\s+)/, '').replace(/!\s*🎉\s*$/, '');
42614      raceNameSpan.textContent = raceName;
42615    }
42616
42617    if (window.RmModal) window.RmModal.open('deletion-confirmation');
42618  }
42619}
42620
42621// Close deletion confirmation modal
42622function closeDeletionConfirmationModal() {
42623  const modal = document.getElementById('deletion-confirmation-modal');
42624  if (window.RmModal && modal) window.RmModal.close(modal);
42625}
42626
42627// Proceed with deletion after confirmation
42628function proceedWithDeletion() {
42629  // Close the deletion confirmation modal
42630  closeDeletionConfirmationModal();
42631  
42632  // Close the main registration modal
42633  closeEnhancedRegistrationModal();
42634  
42635  // Get selected deletion reasons (optional)
42636  const reasons = Array.from(document.querySelectorAll('input[name="deletion_reason"]:checked'))
42637                      .map(input => input.value);
42638
42639  
42640  // Delete ALL registrations for this race (handles multiple registrations)
42641  fetch('/races/unregister', {
42642    method: 'DELETE',
42643    headers: {
42644      'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content,
42645      'Content-Type': 'application/json',
42646      'Accept': 'application/json',
42647      'X-Requested-With': 'XMLHttpRequest'
42648    },
42649    body: JSON.stringify({
42650      id: window.raceId,
42651      reasons: reasons
42652    })
42653  })
42654  .then(response => {
42655
42656    return response.json().then(data => ({ status: response.status, data }));
42657  })
42658  .then(({ status, data }) => {
42659    console.log('Unregister response:', data);  // Debug log
42660    if (status === 200 && data.success) {
42661      // Update button state without page reload - use new data-attribute-aware function
42662      updateButtonToRegisterState();
42663
42664      // Update participant count on search card (search page)
42665      if (data.attendee_count !== undefined && window.raceId) {
42666        updateSearchCardParticipantCount(window.raceId, data.attendee_count);
42667      }
42668
42669      // Update the Who's Going section (race page)
42670      if (data.attendees_html !== undefined) {
42671        console.log('Updating Who\'s Going section with HTML');
42672        updateWhosGoingSection(data.attendees_html);
42673      } else if (data.attendee_count === 0) {
42674        console.log('No attendees left, clearing section');
42675        // If no attendees left, clear the section
42676        const whosGoingContainer = document.getElementById('whos-going-container');
42677        if (whosGoingContainer) {
42678          whosGoingContainer.innerHTML = '';
42679        }
42680      } else {
42681        console.log('No attendees HTML in response, may need to clear manually');
42682        // If we don't have the HTML but know there are 0 attendees, clear it
42683        if (data.attendee_count === 0) {
42684          const whosGoingContainer = document.getElementById('whos-going-container');
42685          if (whosGoingContainer) {
42686            whosGoingContainer.innerHTML = '';
42687          }
42688        }
42689      }
42690
42691      // Clear stored registration data
42692      window.userRegistrationData = null;
42693      // Show success message
42694      showSuccessMessage(data.message || 'Registration removed successfully');
42695    } else {
42696
42697      if (window.UIHelpers && window.UIHelpers.showNotification) {
42698        window.UIHelpers.showNotification('error', 'Registration Error', 'Failed to remove registration: ' + (data.error || 'Unknown error'));
42699      } else if (window.showNotification) {
42700        window.showNotification('error', 'Registration Error', 'Failed to remove registration: ' + (data.error || 'Unknown error'));
42701      } else {
42702        showRegistrationError('Failed to remove registration: ' + (data.error || 'Unknown error'));
42703      }
42704    }
42705  })
42706  .catch(error => {
42707
42708    if (window.UIHelpers && window.UIHelpers.showNotification) {
42709      window.UIHelpers.showNotification('error', 'Error', 'An error occurred while removing registration. Please try again.');
42710    } else if (window.showNotification) {
42711      window.showNotification('error', 'Error', 'An error occurred while removing registration. Please try again.');
42712    } else {
42713      showRegistrationError('An error occurred while removing registration. Please try again.');
42714    }
42715  });
42716}
42717
42718// Show success message
42719function showSuccessMessage(message) {
42720  console.log('Enhanced registration showSuccessMessage called with:', message);
42721  console.trace();  // Show stack trace to find caller
42722  // Create a temporary success notification
42723  const notification = document.createElement('div');
42724  notification.style.cssText = `
42725    position: fixed;
42726    top: 20px;
42727    right: 20px;
42728    background: #10B981;
42729    color: white;
42730    padding: 16px 24px;
42731    border-radius: 8px;
42732    box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
42733    z-index: 10001;
42734    font-weight: 500;
42735    animation: slideInRight 0.3s ease-out;
42736  `;
42737  notification.textContent = message;
42738  
42739  document.body.appendChild(notification);
42740  
42741  // Auto-remove after 3 seconds
42742  setTimeout(() => {
42743    notification.style.animation = 'slideOutRight 0.3s ease-out';
42744    setTimeout(() => {
42745      if (notification.parentNode) {
42746        notification.parentNode.removeChild(notification);
42747      }
42748    }, 300);
42749  }, 3000);
42750}
42751
42752// Submit the registration form
42753function submitRegistration() {
42754
42755  const form = document.getElementById('enhanced-registration-form');
42756  if (!form) {
42757
42758    return;
42759  }
42760  
42761
42762  
42763  // Get form data and log it for debugging
42764  const formData = getFormData();
42765
42766  
42767  // Show loading state on the button
42768  const nextBtn = document.getElementById('next-step-btn');
42769  const originalText = nextBtn.textContent;
42770  
42771  // Determine if we're in edit mode
42772  const isEditMode = window.userRegistrationData || formData.edit_mode;
42773  
42774  nextBtn.textContent = isEditMode ? 'Updating...' : 'Joining...';
42775  nextBtn.disabled = true;
42776  
42777  // Submit form via AJAX
42778  const submitFormData = new FormData(form);
42779
42780  // Add edit mode flag to form data if applicable
42781  if (isEditMode) {
42782    submitFormData.append('edit_mode', 'true');
42783  }
42784  
42785  // Add friend invitation data if present
42786  if (friendInviter && friendInviter.hasSelections()) {
42787    const invitationData = friendInviter.getSelectedData();
42788    
42789    // Add invitation data to form
42790    invitationData.invited_users.forEach(userId => {
42791      submitFormData.append('invitation[invited_users][]', userId);
42792    });
42793    
42794    invitationData.invited_emails.forEach(email => {
42795      submitFormData.append('invitation[invited_emails][]', email);
42796    });
42797    
42798    if (invitationData.invitation_note) {
42799      submitFormData.append('invitation[invitation_note]', invitationData.invitation_note);
42800    }
42801    
42802    submitFormData.append('invitation[context]', invitationData.context);
42803    submitFormData.append('invitation[invitable_type]', invitationData.invitable_type);
42804    submitFormData.append('invitation[invitable_id]', invitationData.invitable_id);
42805  }
42806  
42807  fetch(form.action, {
42808    method: isEditMode ? 'PATCH' : 'POST',
42809    body: submitFormData,
42810    headers: {
42811      'X-Requested-With': 'XMLHttpRequest'
42812    }
42813  })
42814  .then(response => response.json())
42815  .then(data => {
42816    console.log('Registration response:', data);  // Debug log
42817    console.log('Current step before processing:', currentStep);  // Debug
42818    if (data.success) {
42819      if (isEditMode) {
42820        // In edit mode, close modal and show success message
42821        closeEnhancedRegistrationModal();
42822        showSuccessMessage(data.message || 'Registration updated successfully');
42823
42824        // Update the Who's Going section if we have the data
42825        if (data.attendees_html) {
42826          updateWhosGoingSection(data.attendees_html);
42827        }
42828
42829        // Update stored registration data if needed
42830        if (data.registration) {
42831          window.userRegistrationData = data.registration;
42832        }
42833      } else {
42834        // In registration mode, proceed to confirmation
42835        try {
42836          // Update UI elements
42837          updateGoingButton();
42838
42839          // Update the Who's Going section if we have the data
42840          if (data.attendees_html) {
42841            updateWhosGoingSection(data.attendees_html);
42842          } else if (data.attendee_count !== undefined) {
42843            updateWhosGoingCount(data.attendee_count);
42844          }
42845
42846          // IMPORTANT: Reset button state before transitioning
42847          nextBtn.textContent = originalText;
42848          nextBtn.disabled = false;
42849
42850          // Move to confirmation step
42851          console.log('Advancing to step 5...');
42852          currentStep = 5;
42853          console.log('Current step set to:', currentStep);
42854          updateStepDisplay();
42855          console.log('Step display updated');
42856          updateProgressIndicator();
42857          console.log('Progress indicator updated');
42858          updateButtons();
42859          console.log('Buttons updated');
42860          generateRegistrationSummary();
42861          console.log('Registration summary generated');
42862        } catch (error) {
42863          console.error('Error updating after registration:', error);
42864          // Still try to advance to confirmation even if updates fail
42865          currentStep = 5;
42866          updateStepDisplay();
42867          updateProgressIndicator();
42868          updateButtons();
42869          generateRegistrationSummary();
42870        }
42871      }
42872    } else {
42873      if (window.UIHelpers && window.UIHelpers.showNotification) {
42874        window.UIHelpers.showNotification('error', 'Error', data.error || 'Something went wrong. Please try again.');
42875      } else if (window.showNotification) {
42876        window.showNotification('error', 'Error', data.error || 'Something went wrong. Please try again.');
42877      } else {
42878        showRegistrationError(data.error || 'Something went wrong. Please try again.');
42879      }
42880      // Reset button state
42881      nextBtn.textContent = originalText;
42882      nextBtn.disabled = false;
42883    }
42884  })
42885  .catch(error => {
42886
42887    if (window.UIHelpers && window.UIHelpers.showNotification) {
42888      window.UIHelpers.showNotification('error', 'Error', 'Something went wrong. Please try again.');
42889    } else if (window.showNotification) {
42890      window.showNotification('error', 'Error', 'Something went wrong. Please try again.');
42891    } else {
42892      showRegistrationError('Something went wrong. Please try again.');
42893    }
42894    // Reset button state
42895    nextBtn.textContent = originalText;
42896    nextBtn.disabled = false;
42897  });
42898}
42899
42900// Get current form data for storing registration state
42901function getFormData() {
42902  const formData = {};
42903  
42904  // Get participation type
42905  const participationType = document.querySelector('input[name="participation_type"]:checked');
42906  if (participationType) {
42907    formData.participation_type = participationType.value;
42908  }
42909  
42910  // Get selected distances
42911  const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked');
42912  formData.distance_ids = Array.from(selectedDistances).map(input => input.value);
42913  
42914  // In edit mode, detect distances that need to be removed
42915  if (window.userRegistrations && window.userRegistrations.length > 0) {
42916    const originalDistanceIds = window.userRegistrations
42917      .filter(reg => reg.race_distance_id)
42918      .map(reg => reg.race_distance_id.toString());
42919    
42920    // Find distances that were originally selected but are now unselected
42921    const distancesToRemove = originalDistanceIds.filter(id => !formData.distance_ids.includes(id));
42922    
42923    if (distancesToRemove.length > 0) {
42924      formData.remove_distance_ids = distancesToRemove;
42925
42926    }
42927    
42928    // Mark as edit mode
42929    formData.edit_mode = true;
42930
42931
42932  }
42933  
42934  // Get goal data
42935  const goalEffort = document.querySelector('input[name="goal_effort"]:checked');
42936  if (goalEffort) {
42937    formData.goal_effort = goalEffort.value;
42938  }
42939  
42940  formData.goal_pace_value = document.querySelector('input[name="goal_pace_value"]')?.value || '';
42941  formData.goal_pace_unit = document.querySelector('select[name="goal_pace_unit"]')?.value || 'per_mile';
42942  formData.notes = document.querySelector('textarea[name="notes"]')?.value || '';
42943  
42944  // Get visibility setting
42945  const isPublic = document.querySelector('input[name="is_public"]:checked');
42946  formData.is_public = isPublic ? isPublic.value : 'false';
42947  
42948  return formData;
42949}
42950
42951// Update the Going button to registered state without page reload
42952function updateGoingButton() {
42953  const goingBtn = document.querySelector('.btn-going');
42954  if (goingBtn) {
42955    goingBtn.classList.add('registered');
42956    goingBtn.innerHTML = `
42957      <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
42958        <path d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z"/>
42959      </svg>
42960      <span>Going</span>
42961    `;
42962    
42963    // Update the onclick handler to open in edit mode
42964    goingBtn.setAttribute('onclick', 'openEnhancedRegistrationModal(true)');
42965    goingBtn.setAttribute('title', 'Click to edit or remove your registration');
42966    
42967    // Store the registration data globally for edit mode
42968    if (typeof window !== 'undefined') {
42969      window.userRegistrationData = getFormData();
42970    }
42971  }
42972}
42973
42974/**
42975 * @deprecated Use updateButtonToRegisterState() instead - this only targets first .btn-going
42976 * Legacy function kept for backwards compatibility
42977 */
42978function updateGoingButtonToUnregistered() {
42979
42980  const goingBtn = document.querySelector('.btn-going');
42981
42982
42983  
42984  if (goingBtn) {
42985    goingBtn.classList.remove('registered');
42986    goingBtn.innerHTML = `
42987      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
42988        <path d="M13 10V3L4 14h7v7l9-11h-7z"/>
42989      </svg>
42990      <span>I'm Going!</span>
42991    `;
42992    
42993    // Update the onclick handler to open in registration mode
42994    goingBtn.setAttribute('onclick', 'openEnhancedRegistrationModal()');
42995    goingBtn.removeAttribute('title');
42996    
42997    // Clear stored registration data
42998    window.userRegistrationData = null;
42999    
43000
43001
43002
43003  } else {
43004
43005  }
43006}
43007
43008// Navigate to next step
43009function nextStep() {
43010  if (validateCurrentStep()) {
43011    // EARLY REGISTRATION LOGIC: Create registration at Step 1/2 to capture abandonment
43012
43013    // Non-participants: Create registration after Step 1
43014    if (currentStep === 1 && selectedParticipationType !== 'participant') {
43015      submitInitialRegistration();
43016      return;
43017    }
43018
43019    // Participants: Create registration after Step 2 (after selecting distances)
43020    if (currentStep === 2 && selectedParticipationType === 'participant') {
43021      submitInitialRegistration();
43022      return;
43023    }
43024
43025    // If we're on step 4, submit the FINAL registration (update with goals/friends)
43026    if (currentStep === 4) {
43027      updateRegistrationWithGoalsAndFriends();
43028      return;
43029    }
43030
43031    if (currentStep < totalSteps) {
43032      currentStep++;
43033
43034      // Skip steps based on participation type
43035      if (selectedParticipationType !== 'participant') {
43036        // Skip distance and goals steps for non-participants
43037        if (currentStep === 2) currentStep = 4;
43038        if (currentStep === 3) currentStep = 4;
43039      }
43040
43041      updateStepDisplay();
43042      updateProgressIndicator();
43043      updateButtons();
43044    }
43045  }
43046}
43047
43048// Navigate to previous step
43049function previousStep() {
43050  if (currentStep > 1) {
43051    currentStep--;
43052    
43053    // Skip steps based on participation type
43054    if (selectedParticipationType !== 'participant') {
43055      // Skip distance and goals steps for non-participants
43056      if (currentStep === 3) currentStep = 1;
43057      if (currentStep === 2) currentStep = 1;
43058    }
43059    
43060    updateStepDisplay();
43061    updateProgressIndicator();
43062    updateButtons();
43063  }
43064}
43065
43066// Update step visibility
43067function updateStepDisplay() {
43068  // Hide all steps
43069  document.querySelectorAll('.step-content').forEach(step => {
43070    step.style.display = 'none';
43071  });
43072
43073  // Determine which step content to show based on step number and auth state
43074  let stepSelector;
43075  if (currentStep === 3 && authStepActive) {
43076    stepSelector = '[data-step="3-auth"]';  // Show auth step
43077  } else if (currentStep === 3) {
43078    stepSelector = '[data-step="3-goals"]';  // Show goals step
43079  } else if (currentStep === 4 && authStepActive) {
43080    // For non-participants, step 4 should show friends (they skip goals)
43081    // For participants, step 4 should show goals (shifted due to auth step)
43082    if (selectedParticipationType !== 'participant') {
43083      stepSelector = '[data-step="4-friends"]';  // Non-participants skip goals
43084    } else {
43085      stepSelector = '[data-step="3-goals"]';  // Goals shifted to step 4 for participants
43086    }
43087  } else if (currentStep === 4) {
43088    stepSelector = '[data-step="4-friends"]';  // Show friends step
43089  } else if (currentStep === 5 && authStepActive) {
43090    // For non-participants, step 5 should show confirmation
43091    // For participants, step 5 should show friends
43092    if (selectedParticipationType !== 'participant') {
43093      stepSelector = '[data-step="5-confirmation"]';  // Non-participants skip to confirmation
43094    } else {
43095      stepSelector = '[data-step="4-friends"]';  // Friends shifted to step 5 for participants
43096    }
43097  } else if (currentStep === 5) {
43098    stepSelector = '[data-step="5-confirmation"]';  // Show confirmation
43099  } else if (currentStep === 6 && authStepActive) {
43100    stepSelector = '[data-step="5-confirmation"]';  // Confirmation shifted to step 6
43101  } else {
43102    stepSelector = `[data-step="${currentStep}"]`;  // Steps 1 & 2 unchanged
43103  }
43104
43105  // Show current step
43106  const currentStepElement = document.querySelector(`.step-content${stepSelector}`);
43107  if (currentStepElement) {
43108    currentStepElement.style.display = 'block';
43109  }
43110
43111  // Show verification overlay on friends step if user email is not verified
43112  // For participants: friends is step 4 (no auth) or step 5 (with auth)
43113  // For non-participants: friends is step 4 (both with and without auth, since they skip step 2 and 3)
43114  let friendsStepNumber;
43115  if (selectedParticipationType !== 'participant') {
43116    friendsStepNumber = 4; // Non-participants always at step 4
43117  } else {
43118    friendsStepNumber = authStepActive ? 5 : 4; // Participants shift if auth active
43119  }
43120
43121  if (currentStep === friendsStepNumber) {
43122    updateVerificationOverlay();
43123    // Phase 8: Fetch and render teams when reaching friends step
43124    fetchAndRenderTeams();
43125  }
43126
43127  // Update modal header based on current step
43128  updateModalHeader();
43129}
43130
43131/**
43132 * Show/hide verification overlay based on user's email verification status
43133 */
43134function updateVerificationOverlay() {
43135  const modal = document.getElementById('enhanced-registration-modal');
43136  const isVerified = modal && modal.getAttribute('data-user-verified') === 'true';
43137
43138  let overlay = document.getElementById('crew-verification-overlay');
43139  const friendInviter = document.getElementById('enhanced-registration-friend-inviter');
43140
43141  if (!isVerified) {
43142    // Apply blur to friend inviter
43143    if (friendInviter) {
43144      friendInviter.style.filter = 'blur(2px)';
43145      friendInviter.style.pointerEvents = 'none';
43146    }
43147
43148    // Create overlay if it doesn't exist
43149    if (!overlay && friendInviter) {
43150      const userEmail = document.querySelector('[data-user-email]')?.getAttribute('data-user-email') || 'your email';
43151
43152      // Blessed verify-card (mirrors the server-rendered ERB overlay). Button
43153      // uses the shared handler so both paths behave identically.
43154      overlay = document.createElement('div');
43155      overlay.id = 'crew-verification-overlay';
43156      overlay.className = 'verify-overlay verification-required-overlay';
43157      overlay.innerHTML =
43158        '<div class="verify-card">' +
43159          '<i class="fa-solid fa-envelope" id="crew-overlay-icon"></i>' +
43160          '<b id="crew-overlay-title">Verify your email first</b>' +
43161          '<p id="crew-overlay-message">To invite friends to your crew, verify your email address. Check your inbox for the link.</p>' +
43162          '<button type="button" id="crew-verify-btn" class="racemob-btn racemob-btn-secondary racemob-btn--sm" onclick="sendVerificationEmailFromCrewOverlay(this)">Resend verification email</button>' +
43163        '</div>';
43164
43165      // Insert overlay after friend inviter
43166      friendInviter.parentElement.insertBefore(overlay, friendInviter.nextSibling);
43167    }
43168
43169    // Show overlay
43170    if (overlay) {
43171      overlay.style.display = 'block';
43172    }
43173  } else {
43174    // Remove blur from friend inviter
43175    if (friendInviter) {
43176      friendInviter.style.filter = '';
43177      friendInviter.style.pointerEvents = '';
43178    }
43179
43180    // Hide overlay
43181    if (overlay) {
43182      overlay.style.display = 'none';
43183    }
43184  }
43185}
43186
43187// Update modal header based on current step
43188function updateModalHeader() {
43189  const modalTitle = document.querySelector('.enhanced-modal-title');
43190  const modalSubtitle = document.querySelector('.enhanced-modal-header .modal-subtitle');
43191  
43192  if (!modalTitle || !modalSubtitle) return;
43193  
43194  // Get race name from data attribute first, or extract from current title
43195  let raceName = modalTitle.getAttribute('data-race-name');
43196  
43197  if (!raceName) {
43198    // Extract race name from current title, handling all possible formats
43199    raceName = modalTitle.textContent
43200      .replace(/^Update Your Plans for\s+/, '')
43201      .replace(/^Join\s+/, '')
43202      .replace(/!\s*🎉\s*$/, '');
43203  }
43204  
43205  // Store race name if not already stored
43206  if (!modalTitle.getAttribute('data-race-name')) {
43207    modalTitle.setAttribute('data-race-name', raceName);
43208  }
43209  
43210  switch (currentStep) {
43211    case 1:
43212      if (isInEditMode) {
43213        // Keep the edit mode title if already set
43214        if (!modalTitle.textContent.includes('Update your plans')) {
43215          modalTitle.innerHTML = `Update your plans for ${raceName}`;
43216        }
43217        modalSubtitle.textContent = 'Change any details about your registration';
43218      } else {
43219        modalTitle.innerHTML = raceName.startsWith('Join ') ? raceName : `Join ${raceName}`;
43220        modalSubtitle.textContent = "Race day is better with a crew. Two minutes, tell yours you're in.";
43221      }
43222      break;
43223    case 2:
43224      modalTitle.innerHTML = 'Pick your distance';
43225      modalSubtitle.textContent = 'Dream big. You can change this later.';
43226      break;
43227    case 3:
43228      if (authStepActive) {
43229        modalTitle.innerHTML = 'Save your spot';
43230        modalSubtitle.textContent = 'Track your races, find your people. Free forever.';
43231      } else {
43232        modalTitle.innerHTML = 'Set your race goal';
43233        modalSubtitle.textContent = 'Fun run or PR hunt, your crew wants to know';
43234      }
43235      break;
43236    case 4:
43237      if (authStepActive && selectedParticipationType === 'participant') {
43238        // Participants with auth: Step 4 = Goals
43239        modalTitle.innerHTML = 'Set your race goal';
43240        modalSubtitle.textContent = 'Fun run or PR hunt, your crew wants to know';
43241      } else {
43242        // Non-participants OR no auth: Step 4 = Friends
43243        modalTitle.innerHTML = 'Build your race day crew';
43244        modalSubtitle.textContent = 'Racing is a team sport. Rally your people.';
43245      }
43246      break;
43247    case 5:
43248      if (authStepActive && selectedParticipationType === 'participant') {
43249        // Participants with auth: Step 5 = Friends
43250        modalTitle.innerHTML = 'Build your race day crew';
43251        modalSubtitle.textContent = 'Racing is a team sport. Rally your people.';
43252      } else {
43253        // Non-participants OR no auth: Step 5 = Confirmation
43254        modalTitle.innerHTML = '';
43255        modalSubtitle.textContent = '';
43256      }
43257      break;
43258    case 6:
43259      if (authStepActive) {
43260        // Confirmation page - hide header
43261        modalTitle.innerHTML = '';
43262        modalSubtitle.textContent = '';
43263      }
43264      break;
43265  }
43266}
43267
43268// Update progress indicator
43269function updateProgressIndicator() {
43270  document.querySelectorAll('.progress-step').forEach((step, index) => {
43271    const stepNumber = index + 1;
43272    
43273    // For step 5 (confirmation), show all 4 progress steps as completed
43274    if (currentStep === 5) {
43275      step.classList.add('completed');
43276      step.classList.remove('active');
43277    } else if (stepNumber < currentStep) {
43278      step.classList.add('completed');
43279      step.classList.remove('active');
43280    } else if (stepNumber === currentStep) {
43281      step.classList.add('active');
43282      step.classList.remove('completed');
43283    } else {
43284      step.classList.remove('active', 'completed');
43285    }
43286    
43287    // Hide irrelevant steps for non-participants
43288    if (selectedParticipationType !== 'participant' && (stepNumber === 2 || stepNumber === 3)) {
43289      step.style.opacity = '0.3';
43290    } else {
43291      step.style.opacity = '1';
43292    }
43293  });
43294}
43295
43296// Update button visibility and text
43297function updateButtons() {
43298  const prevBtn = document.getElementById('prev-step-btn');
43299  const nextBtn = document.getElementById('next-step-btn');
43300  const submitBtn = document.getElementById('submit-registration-btn');
43301
43302  // CRITICAL: Restore default onclick handlers (from HTML attributes)
43303  // The auth step sets custom handlers, so we need to restore the defaults
43304  if (prevBtn && !prevBtn.getAttribute('onclick')) {
43305    // prevBtn has no default handler, keep it null
43306    prevBtn.onclick = null;
43307  }
43308  if (nextBtn) {
43309    // Special handler for auth step, otherwise use default nextStep()
43310    if (currentStep === 3 && authStepActive) {
43311      nextBtn.onclick = function() {
43312        const activeTab = document.querySelector('.auth-tab.active').dataset.tab;
43313        if (activeTab === 'signup') {
43314          handleInlineSignup();
43315        } else {
43316          handleInlineSignin();
43317        }
43318      };
43319    } else {
43320      // Restore nextBtn to use the nextStep() function from HTML
43321      nextBtn.onclick = function() { nextStep(); };
43322    }
43323  }
43324
43325  // Show/hide previous button
43326  if (currentStep === 1) {
43327    prevBtn.style.display = 'none';
43328  } else {
43329    prevBtn.style.display = 'inline-block';
43330  }
43331
43332  // Show/hide next vs submit button
43333  // Check if we're on the confirmation step
43334  const isOnConfirmationStep = (currentStep === totalSteps) ||
43335    (selectedParticipationType !== 'participant' && currentStep === 5 && authStepActive);
43336
43337  if (isOnConfirmationStep) {
43338    // Confirmation step - different behavior for registration vs edit mode
43339    nextBtn.style.display = 'none';
43340
43341    // In edit mode, show submit button
43342    if (window.userRegistrationData) {
43343      // Edit mode - show "Save All Changes" button
43344      submitBtn.style.display = 'inline-block';
43345      submitBtn.textContent = 'Save All Changes';
43346      prevBtn.style.display = 'inline-block';
43347    } else {
43348      // Registration mode - confirmation step, hide all buttons
43349      submitBtn.style.display = 'none';
43350      prevBtn.style.display = 'none';
43351    }
43352  } else {
43353    nextBtn.style.display = 'inline-block';
43354    submitBtn.style.display = 'none';
43355
43356    // Ensure the Next button is enabled
43357    nextBtn.disabled = false;
43358
43359    // Update Next button text
43360    // For participants: "I'm Ready!" at step 4 (friends step without auth) or step 5 (friends step with auth)
43361    // For non-participants: "Next" at step 4 (friends step)
43362    const isOnFriendsStep = (selectedParticipationType === 'participant' &&
43363                              ((currentStep === 4 && !authStepActive) || (currentStep === 5 && authStepActive))) ||
43364                             (selectedParticipationType !== 'participant' && currentStep === 4);
43365
43366    if (isOnFriendsStep && selectedParticipationType === 'participant') {
43367      nextBtn.textContent = "Join this race";
43368    } else {
43369      nextBtn.textContent = "Continue";
43370    }
43371  }
43372}
43373
43374// Validate current step
43375function validateCurrentStep() {
43376  if (currentStep === 1) {
43377    return validateParticipationType();
43378  } else if (currentStep === 2) {
43379    return validateDistances();
43380  } else if (currentStep === 3 && authStepActive) {
43381    // Auth step - validation happens in handleInlineSignup/Signin
43382    return false;  // Don't auto-advance, let auth handlers do it
43383  } else {
43384    return true;  // Goals, friends, confirmation are all optional
43385  }
43386}
43387
43388// Validate participation type selection
43389function validateParticipationType() {
43390  const selectedType = document.querySelector('input[name="participation_type"]:checked');
43391  if (!selectedType) {
43392    if (window.UIHelpers && window.UIHelpers.showNotification) {
43393      window.UIHelpers.showNotification('warning', 'Selection Required', 'Please select how you\'re participating in this race.');
43394    } else if (window.showNotification) {
43395      window.showNotification('warning', 'Selection Required', 'Please select how you\'re participating in this race.');
43396    } else {
43397      showRegistrationError('Please select how you\'re participating in this race.');
43398    }
43399    return false;
43400  }
43401  
43402  selectedParticipationType = selectedType.value;
43403  updateStepTitlesForParticipationType();
43404  return true;
43405}
43406
43407// Validate distance selection (for participants only)
43408function validateDistances() {
43409  if (selectedParticipationType !== 'participant') {
43410    return true;
43411  }
43412  
43413  const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked');
43414  const customDistance = document.querySelector('input[name="create_custom_distance"]:checked');
43415  const skipDistances = document.querySelector('input[name="skip_distances"]:checked');
43416  
43417  // Clear any previous validation styling
43418  clearDistanceValidation();
43419  
43420  // If they chose to skip distances, that's valid
43421  if (skipDistances) {
43422    return true;
43423  }
43424  
43425  // If they selected a custom distance, validate the custom fields
43426  if (customDistance) {
43427    const customName = document.querySelector('input[name="custom_distance_name"]').value.trim();
43428    if (!customName) {
43429      showCustomDistanceValidation();
43430      return false;
43431    }
43432    return true;
43433  }
43434  
43435  // Otherwise, they need to select at least one regular distance
43436  if (selectedDistances.length === 0) {
43437    highlightSkipDistanceOption();
43438    return false;
43439  }
43440  
43441  return true;
43442}
43443
43444// Update step titles based on participation type
43445function updateStepTitlesForParticipationType() {
43446  const distanceTitle = document.getElementById('distance-step-title');
43447  
43448  // Guard against missing element
43449  if (!distanceTitle) {
43450
43451    return;
43452  }
43453  
43454  switch (selectedParticipationType) {
43455    case 'participant':
43456      distanceTitle.textContent = 'Which distances are you running?';
43457      break;
43458    case 'volunteer':
43459      distanceTitle.textContent = 'Volunteer Information';
43460      break;
43461    case 'spectator':
43462      distanceTitle.textContent = 'Spectator Information';
43463      break;
43464    case 'supporter':
43465      distanceTitle.textContent = 'Supporter Information';
43466      break;
43467    default:
43468      distanceTitle.textContent = 'Event Information';
43469  }
43470}
43471
43472// Add another friend email field
43473function addFriendEmailField() {
43474  const container = document.querySelector('.friend-emails-container');
43475  const newInput = document.createElement('input');
43476  newInput.type = 'email';
43477  newInput.name = 'friend_emails[]';
43478  newInput.placeholder = '[email protected]';
43479  newInput.className = 'friend-email-input';
43480  
43481  // Insert before the "Add Another" button
43482  const addButton = container.querySelector('.add-friend-btn');
43483  container.insertBefore(newInput, addButton);
43484}
43485
43486// Tag management system (scoped to avoid conflicts)
43487if (typeof window.registrationSelectedTags === 'undefined') {
43488  window.registrationSelectedTags = [];  // Suggested tags that are selected
43489}
43490if (typeof window.registrationCustomTags === 'undefined') {
43491  window.registrationCustomTags = [];    // Custom tags added by user
43492}
43493
43494function addCustomTag() {
43495  const input = document.getElementById('custom-tag-input');
43496  const tagText = input.value.trim();
43497  
43498  if (tagText && !window.registrationCustomTags.includes(tagText) && !window.registrationSelectedTags.includes(tagText)) {
43499    window.registrationCustomTags.push(tagText);
43500    input.value = '';
43501    renderCustomTags();
43502    updateTagsForm();
43503  }
43504}
43505
43506function selectSuggestedTag(tagText) {
43507  const suggestedTag = findElementByText('.suggested-tag', tagText);
43508  
43509  if (window.registrationSelectedTags.includes(tagText)) {
43510    // Remove tag
43511    window.registrationSelectedTags = window.registrationSelectedTags.filter(tag => tag !== tagText);
43512    if (suggestedTag) suggestedTag.classList.remove('selected');
43513  } else {
43514    // Add tag
43515    window.registrationSelectedTags.push(tagText);
43516    if (suggestedTag) suggestedTag.classList.add('selected');
43517  }
43518  
43519  updateTagsForm();
43520}
43521
43522function removeTag(tagText) {
43523  // Remove from custom tags only
43524  window.registrationCustomTags = window.registrationCustomTags.filter(tag => tag !== tagText);
43525  
43526  renderCustomTags();
43527  updateTagsForm();
43528}
43529
43530function renderCustomTags() {
43531  const container = document.getElementById('selected-tags-container');
43532  container.innerHTML = '';
43533  
43534  window.registrationCustomTags.forEach(tag => {
43535    const tagElement = document.createElement('span');
43536    tagElement.className = 'selected-tag';
43537    tagElement.innerHTML = `
43538      ${tag}
43539      <button type="button" class="remove-tag" onclick="removeTag('${tag.replace(/'/g, "\\'")}')">×</button>
43540    `;
43541    container.appendChild(tagElement);
43542  });
43543}
43544
43545function updateTagsForm() {
43546  // Remove existing hidden inputs
43547  document.querySelectorAll('input[name="goal_tags[]"]').forEach(input => input.remove());
43548  
43549  // Add hidden inputs for all tags (both selected suggested and custom)
43550  const form = document.getElementById('enhanced-registration-form');
43551  const allTags = [...window.registrationSelectedTags, ...window.registrationCustomTags];
43552  
43553  allTags.forEach(tag => {
43554    const hiddenInput = document.createElement('input');
43555    hiddenInput.type = 'hidden';
43556    hiddenInput.name = 'goal_tags[]';
43557    hiddenInput.value = tag;
43558    form.appendChild(hiddenInput);
43559  });
43560}
43561
43562// Helper function for finding elements by text content
43563function findElementByText(selector, text) {
43564  const elements = document.querySelectorAll(selector);
43565  return Array.from(elements).find(el => el.textContent.trim() === text);
43566}
43567
43568// Generate registration summary for confirmation step
43569function generateRegistrationSummary() {
43570  // Update the detail fields directly
43571  updateSummaryRole();
43572  // Get currently selected team name from dropdown
43573  const selectedOption = document.querySelector('.team-select-option.selected');
43574  const teamName = selectedOption?.dataset?.teamName || null;
43575  updateSummaryTeam(teamName);
43576  updateSummaryDistance();
43577  updateSummaryVibe();
43578  updateSummaryPace();
43579  updateSummaryFriends();
43580}
43581
43582// Update role summary
43583function updateSummaryRole() {
43584  const roleElement = document.getElementById('summary-role');
43585  if (!roleElement) return;
43586
43587  const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant';
43588  const typeEmoji = getParticipationTypeEmoji(participationType);
43589  const typeText = participationType.charAt(0).toUpperCase() + participationType.slice(1);
43590
43591  roleElement.textContent = `${typeEmoji} ${typeText}`.trim();
43592}
43593
43594// Update team summary (Phase 8) - accepts team name from dropdown selection
43595function updateSummaryTeam(teamName) {
43596  const teamRow = document.getElementById('summary-team-row');
43597  const teamElement = document.getElementById('summary-team');
43598  if (!teamRow || !teamElement) return;
43599
43600  const primaryTeamId = document.getElementById('primary-team-id-input')?.value;
43601
43602  if (teamName && teamName !== 'Racing independently' && primaryTeamId) {
43603    teamRow.style.display = 'flex';
43604    teamElement.textContent = teamName;
43605  } else {
43606    teamRow.style.display = 'none';
43607  }
43608}
43609
43610// Update distance summary
43611function updateSummaryDistance() {
43612  const distanceElement = document.getElementById('summary-distance');
43613  if (!distanceElement) return;
43614
43615  const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant';
43616
43617  if (participationType !== 'participant') {
43618    distanceElement.textContent = '-';
43619    return;
43620  }
43621
43622  // First try to use stored distance names from submission
43623  if (window.selectedDistanceNames) {
43624    distanceElement.textContent = window.selectedDistanceNames;
43625    return;
43626  }
43627
43628  const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked');
43629  const customDistance = document.querySelector('input[name="create_custom_distance"]:checked');
43630  const skipDistances = document.querySelector('input[name="skip_distances"]:checked');
43631
43632  if (skipDistances) {
43633    distanceElement.textContent = 'Will specify later';
43634  } else if (customDistance) {
43635    const customName = document.querySelector('input[name="custom_distance_name"]')?.value || 'Custom Distance';
43636    distanceElement.textContent = customName;
43637  } else if (selectedDistances.length > 0) {
43638    const distanceNames = Array.from(selectedDistances).map(distance => {
43639      const label = distance.closest('label');
43640      const distanceNameElement = label ? label.querySelector('.distance-name') : null;
43641      return distanceNameElement ? distanceNameElement.textContent.trim() : 'Unknown';
43642    });
43643    distanceElement.textContent = distanceNames.join(', ');
43644  } else if (window.userRegistrationData && window.userRegistrationData.distances) {
43645    // Fallback: if in edit mode and no distances checked, use stored data
43646    distanceElement.textContent = window.userRegistrationData.distances;
43647  } else {
43648    distanceElement.textContent = '-';
43649  }
43650}
43651
43652// Update vibe summary
43653function updateSummaryVibe() {
43654  const vibeElement = document.getElementById('summary-vibe');
43655  if (!vibeElement) return;
43656  
43657  const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant';
43658  
43659  if (participationType !== 'participant') {
43660    vibeElement.textContent = '-';
43661    return;
43662  }
43663  
43664  let vibeText = '';
43665  
43666  // Get effort level
43667  const goalEffort = document.querySelector('input[name="goal_effort"]:checked');
43668  if (goalEffort) {
43669    const effortEmoji = getGoalEffortEmoji(goalEffort.value);
43670    const effortText = goalEffort.value.charAt(0).toUpperCase() + goalEffort.value.slice(1);
43671    vibeText = `${effortEmoji} ${effortText}`.trim();
43672  }
43673  
43674  // Get selected tags
43675  const allTags = [...window.registrationSelectedTags, ...window.registrationCustomTags];
43676  if (allTags.length > 0) {
43677    if (vibeText) vibeText += ' • ';
43678    vibeText += allTags.join(', ');
43679  }
43680  
43681  vibeElement.textContent = vibeText || '-';
43682}
43683
43684// Update pace summary
43685function updateSummaryPace() {
43686  const paceElement = document.getElementById('summary-pace');
43687  if (!paceElement) return;
43688  
43689  const participationType = document.querySelector('input[name="participation_type"]:checked')?.value || 'participant';
43690  
43691  if (participationType !== 'participant') {
43692    paceElement.textContent = '-';
43693    return;
43694  }
43695  
43696  const goalPace = document.querySelector('input[name="goal_pace_value"]')?.value;
43697  if (goalPace && goalPace.trim()) {
43698    const paceUnit = document.querySelector('select[name="goal_pace_unit"]')?.value || 'per_mile';
43699    const unitText = paceUnit === 'per_mile' ? ' per mile' : ' per km';
43700    paceElement.textContent = goalPace + unitText;
43701  } else {
43702    paceElement.textContent = '-';
43703  }
43704}
43705
43706// Update friends summary
43707function updateSummaryFriends() {
43708  const friendsElement = document.getElementById('summary-friends');
43709  if (!friendsElement) return;
43710  
43711  let invitationCount = 0;
43712  
43713  // Count friend inviter selections if available
43714  if (friendInviter && friendInviter.hasSelections()) {
43715    const invitationData = friendInviter.getSelectedData();
43716    invitationCount = (invitationData.invited_users?.length || 0) + (invitationData.invited_emails?.length || 0);
43717  }
43718  
43719  // Fallback: count manual email inputs (if that system exists)
43720  const friendEmails = Array.from(document.querySelectorAll('input[name="friend_emails[]"]'))
43721    .map(input => input.value.trim())
43722    .filter(email => email.length > 0);
43723  
43724  if (friendEmails.length > 0) {
43725    invitationCount += friendEmails.length;
43726  }
43727  
43728  if (invitationCount > 0) {
43729    friendsElement.textContent = `${invitationCount} invitation${invitationCount > 1 ? 's' : ''} sent`;
43730  } else {
43731    friendsElement.textContent = '0 invitations sent';
43732  }
43733}
43734
43735// Emoji are barred in product UI (DESIGN-STANDARDS rule 4). These helpers stay
43736// (call sites expect them) but return nothing; summaries read as plain text.
43737function getParticipationTypeEmoji(type) {
43738  return '';
43739}
43740
43741function getGoalEffortEmoji(effort) {
43742  return '';
43743}
43744
43745// Form submission handler and event listeners
43746function initEnhancedRegistrationForm() {
43747  const form = document.getElementById('enhanced-registration-form');
43748
43749  // Prevent double initialization
43750  if (form && form.dataset.initialized === 'true') return;
43751
43752  // Add Enter key support for custom tag input
43753  const customTagInput = document.getElementById('custom-tag-input');
43754  if (customTagInput) {
43755    customTagInput.addEventListener('keypress', function(e) {
43756      if (e.key === 'Enter') {
43757        e.preventDefault();
43758        addCustomTag();
43759      }
43760    });
43761  }
43762  if (form) {
43763    form.dataset.initialized = 'true';
43764    form.addEventListener('submit', function(e) {
43765      e.preventDefault();
43766      // Call submitRegistration when form is submitted (e.g., via Save All Changes button)
43767      submitRegistration();
43768    });
43769
43770    // Custom distance checkbox handler
43771    const customDistanceCheckbox = document.getElementById('custom-distance-checkbox');
43772    const customDistanceInput = document.getElementById('custom-distance-input');
43773
43774    if (customDistanceCheckbox && customDistanceInput) {
43775      customDistanceCheckbox.addEventListener('change', function() {
43776        if (this.checked) {
43777          customDistanceInput.style.display = 'flex';
43778          // Uncheck skip distances if custom is selected
43779          const skipCheckbox = document.getElementById('skip-distances-checkbox');
43780          if (skipCheckbox) skipCheckbox.checked = false;
43781        } else {
43782          customDistanceInput.style.display = 'none';
43783        }
43784      });
43785    }
43786
43787    // Skip distances checkbox handler
43788    const skipDistancesCheckbox = document.getElementById('skip-distances-checkbox');
43789
43790    if (skipDistancesCheckbox) {
43791      skipDistancesCheckbox.addEventListener('change', function() {
43792        if (this.checked) {
43793          // Uncheck all distance selections and custom distance
43794          document.querySelectorAll('input[name="distance_ids[]"]').forEach(cb => cb.checked = false);
43795          if (customDistanceCheckbox) {
43796            customDistanceCheckbox.checked = false;
43797            customDistanceInput.style.display = 'none';
43798          }
43799        }
43800      });
43801    }
43802
43803    // Regular distance checkboxes handler
43804    document.querySelectorAll('input[name="distance_ids[]"]').forEach(checkbox => {
43805      checkbox.addEventListener('change', function() {
43806        if (this.checked) {
43807          // Uncheck skip distances if any regular distance is selected
43808          if (skipDistancesCheckbox) skipDistancesCheckbox.checked = false;
43809        }
43810      });
43811    });
43812  }
43813}
43814
43815// Turbolinks lifecycle
43816document.addEventListener('turbolinks:load', initEnhancedRegistrationForm);
43817
43818// Turbolinks cleanup - reset initialization flags before cache
43819document.addEventListener('turbolinks:before-cache', function() {
43820  var form = document.getElementById('enhanced-registration-form');
43821  if (form) delete form.dataset.initialized;
43822});
43823
43824// Fallback for non-Turbolinks pages
43825if (!window.Turbolinks) {
43826  document.addEventListener('DOMContentLoaded', initEnhancedRegistrationForm);
43827}
43828
43829// Close modal when clicking outside
43830document.addEventListener('click', function(e) {
43831  if (e.target.classList.contains('enhanced-modal-overlay')) {
43832    closeEnhancedRegistrationModal();
43833  }
43834});
43835
43836// Close modal with Escape key
43837document.addEventListener('keydown', function(e) {
43838  if (e.key === 'Escape') {
43839    closeEnhancedRegistrationModal();
43840  }
43841});
43842
43843// Update the Who's Going section with HTML from server
43844function updateWhosGoingSection(html) {
43845  // Find the Who's Going container on the page
43846  const whosGoingContainer = document.getElementById('whos-going-container');
43847  if (whosGoingContainer && html) {
43848    whosGoingContainer.innerHTML = html;
43849    // Make sure the container is visible
43850    whosGoingContainer.style.display = 'block';
43851  }
43852}
43853
43854// Update just the count in the Who's Going section
43855function updateWhosGoingCount(count) {
43856  // Update any attendee count displays
43857  const countElements = document.querySelectorAll('.attendee-count, .going-count, [data-attendee-count]');
43858  countElements.forEach(el => {
43859    el.textContent = count;
43860  });
43861
43862  // Also update the global count if it exists
43863  if (typeof window !== 'undefined') {
43864    window.raceMobbersGoingCount = count;
43865  }
43866}
43867
43868// Update the RaceMobbers going section with dynamic count
43869function updateRaceMobbersGoingSection() {
43870  // This would typically come from an API call, for now using a placeholder
43871  // In a real implementation, you'd fetch this from the server
43872  const raceMobbersCount = getRaceMobbersCount();
43873  
43874  const section = document.getElementById('racemobbers-going-section');
43875  const countText = document.getElementById('racemobbers-count-text');
43876  
43877  if (!section || !countText) return;
43878  
43879  if (raceMobbersCount === 0) {
43880    // Hide the section if no one is going yet
43881    section.style.display = 'none';
43882  } else {
43883    section.style.display = 'flex';
43884    
43885    let text = '';
43886    if (raceMobbersCount === 1) {
43887      text = '1 RaceMobber is already going!';
43888    } else {
43889      text = `${raceMobbersCount} RaceMobbers are already going!`;
43890    }
43891    
43892    countText.textContent = text;
43893  }
43894}
43895
43896// Get the actual RaceMobbers count
43897function getRaceMobbersCount() {
43898  // Use the actual count from the server if available
43899  if (typeof window.raceMobbersGoingCount !== 'undefined') {
43900    return window.raceMobbersGoingCount;
43901  }
43902  // Fallback to 0 if not available
43903  return 0;
43904}
43905
43906// Clear distance validation styling
43907function clearDistanceValidation() {
43908  const skipSection = document.querySelector('.distance-skip-section');
43909  const customInput = document.querySelector('.custom-distance-input');
43910  
43911  if (skipSection) {
43912    skipSection.classList.remove('validation-highlight');
43913  }
43914  if (customInput) {
43915    customInput.classList.remove('validation-error');
43916  }
43917}
43918
43919// Highlight skip distance option as suggestion
43920function highlightSkipDistanceOption() {
43921  const skipSection = document.querySelector('.distance-skip-section');
43922  if (skipSection) {
43923    skipSection.classList.add('validation-highlight');
43924    // Remove highlight after 3 seconds
43925    setTimeout(() => {
43926      skipSection.classList.remove('validation-highlight');
43927    }, 3000);
43928  }
43929}
43930
43931// Show custom distance validation error
43932function showCustomDistanceValidation() {
43933  const customInput = document.querySelector('.custom-distance-input');
43934  if (customInput) {
43935    customInput.classList.add('validation-error');
43936    // Focus on the custom distance name field
43937    const nameField = customInput.querySelector('input[name="custom_distance_name"]');
43938    if (nameField) {
43939      nameField.focus();
43940    }
43941  }
43942}
43943
43944// Show "Can't Make it" button in edit mode
43945function showCantMakeItButton() {
43946  const cantMakeItBtn = document.getElementById('cant-make-it-btn');
43947  if (cantMakeItBtn) {
43948    cantMakeItBtn.style.display = 'block';
43949
43950  }
43951}
43952
43953// Hide "Can't Make it" button in registration mode
43954function hideCantMakeItButton() {
43955  const cantMakeItBtn = document.getElementById('cant-make-it-btn');
43956  if (cantMakeItBtn) {
43957    cantMakeItBtn.style.display = 'none';
43958
43959  }
43960}
43961
43962// Update submit button for edit mode
43963function updateSubmitButtonForEditMode() {
43964  const submitBtn = document.getElementById('submit-registration-btn');
43965  if (submitBtn) {
43966    submitBtn.textContent = 'Save All Changes';
43967
43968  }
43969}
43970
43971// Update submit button for registration mode
43972function updateSubmitButtonForRegistrationMode() {
43973  const submitBtn = document.getElementById('submit-registration-btn');
43974  if (submitBtn) {
43975    submitBtn.textContent = 'Join This Race!';
43976
43977  }
43978}
43979
43980// ============================================================================
43981// SEARCH PAGE REGISTRATION MODAL FUNCTIONS
43982// ============================================================================
43983
43984/**
43985 * Open registration modal from search page
43986 * Fetches race data via AJAX and populates modal
43987 * @param {number} raceId - Race occurence ID
43988 */
43989window.openSearchRegistrationModal = function(raceId) {
43990  // Store the race ID globally for use in form submission
43991  window.raceId = raceId;
43992
43993  // Fetch race data from modal_data endpoint
43994  fetch(`/races/${raceId}/modal_data`, {
43995    method: 'GET',
43996    headers: {
43997      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || ''
43998    },
43999    credentials: 'same-origin'
44000  })
44001  .then(response => {
44002    if (!response.ok) {
44003      throw new Error(`HTTP error! status: ${response.status}`);
44004    }
44005    return response.json();
44006  })
44007  .then(data => {
44008    // Sync auth state from server response BEFORE any gating logic
44009    if (data.logged_in !== undefined) {
44010      document.body.dataset.userLoggedIn = data.logged_in ? 'true' : 'false';
44011    }
44012
44013    // CRITICAL ORDER per spec: Reset BEFORE populating
44014    // Step 1: Check if edit mode (user already registered)
44015    const isEditModeFlag = !!data.registration;
44016    if (data.registration) {
44017      // Store registration data globally for edit mode
44018      window.userRegistrationData = data.registration;
44019    }
44020
44021    // Step 2: Reset modal to clean state (before populate)
44022    if (!isEditModeFlag) {
44023      // For new registration: full reset clears form and state
44024      resetModal();
44025    } else {
44026      // For edit mode: just reset state variables, keep form for population
44027      currentStep = 1;
44028      selectedParticipationType = 'participant';
44029      isInEditMode = true;
44030    }
44031
44032    // Step 3: Populate modal with race data (distances, etc.)
44033    populateModalWithRaceData(data);
44034
44035    // Step 4: If edit mode, populate form with existing registration data
44036    if (isEditModeFlag) {
44037      populateRegistrationForm(data.registration);
44038    }
44039
44040    // Step 5: Update UI state
44041    updateStepDisplay();
44042    updateProgressIndicator();
44043    updateButtons();
44044
44045    // Step 6: Open modal (skipReset=true since we already called resetModal above)
44046    openEnhancedRegistrationModal(isEditModeFlag, true);
44047  })
44048  .catch(error => {
44049    console.error('Error fetching race data:', error);
44050    showRegistrationError('Failed to load race information. Please try again.');
44051  });
44052};
44053
44054/**
44055 * Populate modal with race data from AJAX response
44056 * @param {Object} data - Race data from modal_data endpoint
44057 */
44058function populateModalWithRaceData(data) {
44059  // Update modal title
44060  const titleElement = document.querySelector('.enhanced-modal-title');
44061  if (titleElement && data.name) {
44062    titleElement.textContent = data.name;
44063  }
44064
44065  // Update form action URL
44066  const form = document.getElementById('enhanced-registration-form');
44067  if (form) {
44068    form.action = `/races/${data.id}/register`;
44069  }
44070
44071  // Populate distances in Step 2
44072  const distancesContainer = document.getElementById('distances-container');
44073  if (distancesContainer && data.distances && data.distances.length > 0) {
44074    // Save custom distance elements (both checkbox and input) before clearing
44075    const customOption = distancesContainer.querySelector('.custom-distance-option');
44076    const customInput = distancesContainer.querySelector('.custom-distance-input');
44077
44078    // Clear existing distances
44079    distancesContainer.innerHTML = '';
44080
44081    // Add fetched distances
44082    data.distances.forEach((distance, index) => {
44083      const label = document.createElement('label');
44084      label.className = 'distance-option';
44085
44086      const checkbox = document.createElement('input');
44087      checkbox.type = 'checkbox';
44088      checkbox.name = 'distance_ids[]';
44089      checkbox.value = distance.id;
44090      checkbox.id = `distance-${distance.id}`;
44091
44092      // Check first distance by default
44093      if (index === 0) {
44094        checkbox.checked = true;
44095        checkbox.setAttribute('checked', 'checked');
44096      }
44097
44098      const content = document.createElement('div');
44099      content.className = 'distance-content';
44100
44101      const name = document.createElement('span');
44102      name.className = 'distance-name';
44103      name.textContent = distance.distance_name;
44104
44105      const details = document.createElement('span');
44106      details.className = 'distance-details';
44107      details.textContent = distance.distance_in_words;
44108
44109      content.appendChild(name);
44110      content.appendChild(details);
44111
44112      if (distance.price) {
44113        const price = document.createElement('span');
44114        price.className = 'distance-price';
44115        price.textContent = `$${distance.price}`;
44116        content.appendChild(price);
44117      }
44118
44119      label.appendChild(checkbox);
44120      label.appendChild(content);
44121      label.setAttribute('for', `distance-${distance.id}`);
44122      distancesContainer.appendChild(label);
44123    });
44124
44125    // Re-add custom distance option AND input at the end
44126    if (customOption) {
44127      distancesContainer.appendChild(customOption);
44128    }
44129    if (customInput) {
44130      distancesContainer.appendChild(customInput);
44131
44132      // Re-bind custom distance checkbox handler
44133      const customCheckbox = customOption?.querySelector('#custom-distance-checkbox');
44134      if (customCheckbox) {
44135        customCheckbox.addEventListener('change', function() {
44136          customInput.style.display = this.checked ? 'block' : 'none';
44137        });
44138      }
44139    }
44140  }
44141
44142  // Update participants count if available
44143  if (data.participants_count) {
44144    // Find any elements showing participant count and update them
44145    const countElements = document.querySelectorAll('[data-participants-count]');
44146    countElements.forEach(el => {
44147      el.textContent = `${data.participants_count} going`;
44148    });
44149  }
44150}
44151
44152/**
44153 * Update button to "Going" state after successful registration
44154 * Targets both search card buttons and race page buttons
44155 */
44156function updateButtonToGoingState() {
44157  if (!window.raceId) return;
44158
44159  // Try search card button first (has data attributes)
44160  let button = document.querySelector(`[data-race-id="${window.raceId}"][data-action="toggle-registration"]`);
44161
44162  // Fallback to race page button (btn-going in hero section)
44163  if (!button) {
44164    button = document.querySelector('.btn-going');
44165  }
44166
44167  if (!button) return;
44168
44169  // Update button state
44170  button.classList.add('registered');
44171  button.setAttribute('data-registered', 'true');
44172
44173  // Update button content - preserve size attributes from original
44174  const isLargeButton = button.querySelector('svg[width="20"]');
44175  const svgSize = isLargeButton ? '20' : '16';
44176
44177  button.innerHTML = `
44178    <svg width="${svgSize}" height="${svgSize}" viewBox="0 0 24 24" fill="currentColor">
44179      <path d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.38-.66.19-.34.05-.08.07-.12C8.48 10.94 10.42 7.54 13 3h1l-1 7h3.5c.49 0 .56.33.47.51l-.07.15C12.96 17.55 11 21 11 21z"/>
44180    </svg>
44181    <span>Going</span>
44182  `;
44183
44184  // Update onclick - for race page, use the modal's global function
44185  button.onclick = function(e) {
44186    e.stopPropagation();
44187    if (typeof openSearchRegistrationModal === 'function') {
44188      openSearchRegistrationModal(window.raceId);
44189    } else if (typeof openEnhancedRegistrationModal === 'function') {
44190      openEnhancedRegistrationModal(true); // Edit mode
44191    }
44192  };
44193
44194  // Update title
44195  button.title = "You're going to this race! Click to edit registration";
44196}
44197
44198/**
44199 * Update button back to "I'm Going!" state after deletion
44200 * Targets both search card buttons and race page buttons
44201 */
44202function updateButtonToRegisterState() {
44203  if (!window.raceId) return;
44204
44205  // Try search card button first (has data attributes)
44206  let button = document.querySelector(`[data-race-id="${window.raceId}"][data-action="toggle-registration"]`);
44207
44208  // Fallback to race page button (btn-going in hero section)
44209  if (!button) {
44210    button = document.querySelector('.btn-going');
44211  }
44212
44213  if (!button) return;
44214
44215  // Update button state
44216  button.classList.remove('registered');
44217  button.setAttribute('data-registered', 'false');
44218
44219  // Update button content - preserve size attributes from original
44220  const isLargeButton = button.querySelector('svg[width="20"]');
44221  const svgSize = isLargeButton ? '20' : '16';
44222
44223  button.innerHTML = `
44224    <svg width="${svgSize}" height="${svgSize}
44224" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
44225      <path d="M13 10V3L4 14h7v7l9-11h-7z"/>
44226    </svg>
44227    <span>I'm Going!</span>
44228  `;
44229
44230  // Update onclick to open in new registration mode
44231  button.onclick = function(e) {
44232    e.stopPropagation();
44233    if (typeof openSearchRegistrationModal === 'function') {
44234      openSearchRegistrationModal(window.raceId);
44235    } else if (typeof openEnhancedRegistrationModal === 'function') {
44236      openEnhancedRegistrationModal(false); // New registration mode
44237    }
44238  };
44239
44240  // Update title
44241  button.title = "Register for this race";
44242}
44243
44244/**
44245 * Update participant count on search result card
44246 * @param {number} raceId - The race ID
44247 * @param {number} count - The new participant count
44248 */
44249function updateSearchCardParticipantCount(raceId, count) {
44250  // Find the race card on search page
44251  const raceCard = document.querySelector(`[data-race-id="${raceId}"]`);
44252  if (!raceCard) return;
44253
44254  // Find the community section
44255  const communitySection = raceCard.querySelector('.race-community');
44256  if (!communitySection) {
44257    // No community section exists - create it only if count > 0
44258    if (count > 0) {
44259      const newSection = document.createElement('div');
44260      newSection.className = 'race-community';
44261      newSection.innerHTML = `
44262        <div class="community-stat highlight">
44263          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
44264            <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
44265            <circle cx="8.5" cy="7" r="4"/>
44266            <path d="M20 8v6M23 11h-6"/>
44267          </svg>
44268          ${count}
44269        </div>
44270      `;
44271
44272      // Insert after race-footer or at the end of card
44273      const raceFooter = raceCard.querySelector('.race-footer');
44274      if (raceFooter) {
44275        raceFooter.insertAdjacentElement('afterend', newSection);
44276      } else {
44277        raceCard.appendChild(newSection);
44278      }
44279    }
44280    return;
44281  }
44282
44283  // Community section exists - find or create the "going" stat
44284  let goingStat = communitySection.querySelector('.community-stat.highlight');
44285
44286  if (goingStat) {
44287    if (count === 0) {
44288      // Remove the going stat when count drops to zero
44289      goingStat.remove();
44290
44291      // If community section is now empty, remove it too
44292      if (communitySection.children.length === 0) {
44293        communitySection.remove();
44294      }
44295    } else {
44296      // Update existing count - preserve the SVG
44297      const svg = goingStat.querySelector('svg');
44298      if (svg) {
44299        goingStat.innerHTML = svg.outerHTML + ' ' + count;
44300      } else {
44301        goingStat.textContent = count;
44302      }
44303    }
44304  } else if (count > 0) {
44305    // Create new "going" stat as first child
44306    const newStat = document.createElement('div');
44307    newStat.className = 'community-stat highlight';
44308    newStat.innerHTML = `
44309      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
44310        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
44311        <circle cx="8.5" cy="7" r="4"/>
44312        <path d="M20 8v6M23 11h-6"/>
44313      </svg>
44314      ${count}
44315    `;
44316    communitySection.insertBefore(newStat, communitySection.firstChild);
44317  }
44318}
44319
44320// ============================================================================
44321// EARLY REGISTRATION & AUTH GATING FUNCTIONS
44322// ============================================================================
44323
44324/**
44325 * Check if user is currently logged in
44326 * @returns {boolean}
44327 */
44328function isUserLoggedIn() {
44329  return document.body.dataset.userLoggedIn === 'true';
44330}
44331
44332/**
44333 * Show auth modal, then create registration after successful login
44334 */
44335function showAuthModalThenCreateRegistration() {
44336  // Store intent in sessionStorage
44337  sessionStorage.setItem('pendingRegistrationIntent', JSON.stringify({
44338    raceId: window.raceId,
44339    participationType: selectedParticipationType,
44340    distanceIds: getSelectedDistanceIds(),
44341    step: currentStep
44342  }));
44343
44344  // Show auth modal (assuming window.AuthModal exists)
44345  if (window.AuthModal && window.AuthModal.show) {
44346    window.AuthModal.show({
44347      defaultTab: 'signup',
44348      intendedAction: 'register',
44349      onSuccess: function() {
44350        // Update auth state
44351        document.body.dataset.userLoggedIn = 'true';
44352
44353        // Retrieve intent and create registration
44354        const intent = JSON.parse(sessionStorage.getItem('pendingRegistrationIntent') || '{}');
44355        sessionStorage.removeItem('pendingRegistrationIntent');
44356
44357        if (intent.raceId) {
44358          submitInitialRegistration();
44359        }
44360      }
44361    });
44362  } else {
44363    // Fallback: Redirect to login page
44364    window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname);
44365  }
44366}
44367
44368/**
44369 * Get selected distance IDs from Step 2
44370 * @returns {Array<number>}
44371 */
44372function getSelectedDistanceIds() {
44373  const checkboxes = document.querySelectorAll('input[name="distance_ids[]"]:checked');
44374  return Array.from(checkboxes).map(cb => parseInt(cb.value));
44375}
44376
44377/**
44378 * Submit initial registration (Step 1/2)
44379 * Creates registration early to capture abandonment
44380 */
44381function submitInitialRegistration() {
44382  const form = document.getElementById('enhanced-registration-form');
44383  if (!form || !window.raceId) return;
44384
44385  // Collect form data for initial registration
44386  const formData = new FormData();
44387  formData.append('authenticity_token', document.querySelector('input[name="authenticity_token"]')?.value || '');
44388  formData.append('participation_type', selectedParticipationType);
44389
44390  // Add distances if participant
44391  if (selectedParticipationType === 'participant') {
44392    const distanceIds = getSelectedDistanceIds();
44393    distanceIds.forEach(id => formData.append('distance_ids[]', id));
44394
44395    // Store distance names for summary display
44396    const selectedDistances = document.querySelectorAll('input[name="distance_ids[]"]:checked');
44397    const distanceNames = Array.from(selectedDistances).map(distance => {
44398      const label = distance.closest('label');
44399      const distanceNameElement = label ? label.querySelector('.distance-name') : null;
44400      return distanceNameElement ? distanceNameElement.textContent.trim() : null;
44401    }).filter(name => name !== null);
44402    window.selectedDistanceNames = distanceNames.join(', ');
44403  }
44404
44405  // Submit via AJAX (POST to create new registration)
44406  fetch(`/races/${window.raceId}/register`, {
44407    method: 'POST',
44408    body: formData,
44409    headers: {
44410      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || '',
44411      'X-Requested-With': 'XMLHttpRequest'
44412    },
44413    credentials: 'same-origin'
44414  })
44415  .then(response => {
44416    // Handle 401 Unauthorized - user needs to log in
44417    if (response.status === 401) {
44418      return response.json().then(data => {
44419        // Activate auth step and show it
44420        authStepActive = true;
44421        totalSteps = 6;  // Add one step for auth
44422        currentStep = 3;  // Go to auth step
44423        updateStepDisplay();
44424        updateProgressIndicator();
44425        updateButtons();
44426        return null; // Signal auth required
44427      });
44428    }
44429
44430    if (!response.ok) {
44431      throw new Error(`HTTP error! status: ${response.status}`);
44432    }
44433    return response.json();
44434  })
44435  .then(data => {
44436    if (!data) return; // Auth required, already handled
44437
44438    if (data.success) {
44439      // Store registration ID for later update
44440      window.registrationId = data.registration_id;
44441
44442      // Update auth state after successful registration
44443      document.body.dataset.userLoggedIn = 'true';
44444
44445      // Update button state to show "Going" after Step 2 (initial registration is complete)
44446      // For participants: Registration complete after selecting distance (Step 2)
44447      // For non-participants: Registration complete after selecting type (Step 1)
44448      updateButtonToGoingState();
44449
44450      // Update Who's Going section if HTML provided (race page)
44451      if (data.attendees_html) {
44452        const whosGoingContainer = document.getElementById('whos-going-container');
44453        if (whosGoingContainer) {
44454          whosGoingContainer.innerHTML = data.attendees_html;
44455        }
44456      }
44457
44458      // Update participant count on search card (search page)
44459      if (data.attendee_count !== undefined && window.raceId) {
44460        updateSearchCardParticipantCount(window.raceId, data.attendee_count);
44461      }
44462
44463      // Move to next step
44464      currentStep++;
44465      if (selectedParticipationType !== 'participant') {
44466        // Skip to Step 4 for non-participants
44467        currentStep = 4;
44468      }
44469      updateStepDisplay();
44470      updateProgressIndicator();
44471      updateButtons();
44472    } else {
44473      showRegistrationError(data.error || 'Failed to save registration');
44474    }
44475  })
44476  .catch(error => {
44477    console.error('Error submitting initial registration:', error);
44478    showRegistrationError('Failed to save registration. Please try again.');
44479  });
44480}
44481
44482/**
44483 * Update existing registration with goals and friends (Step 4)
44484 * Uses PATCH to update the registration created at Step 1/2
44485 */
44486function updateRegistrationWithGoalsAndFriends() {
44487  const form = document.getElementById('enhanced-registration-form');
44488  if (!form || !window.raceId) return;
44489
44490  // Collect form data for final update
44491  const formData = new FormData();
44492  formData.append('authenticity_token', document.querySelector('input[name="authenticity_token"]')?.value || '');
44493
44494  // Goals (Step 3)
44495  const goalEffort = document.getElementById('goal-effort')?.value;
44496  const goalPaceValue = document.getElementById('goal-pace-value')?.value;
44497  const goalPaceUnit = document.getElementById('goal-pace-unit')?.value;
44498  const notes = document.getElementById('race-notes')?.value;
44499  const isPublic = document.getElementById('make-public')?.checked;
44500
44501  if (goalEffort) formData.append('goal_effort', goalEffort);
44502  if (goalPaceValue) formData.append('goal_pace_value', goalPaceValue);
44503  if (goalPaceUnit) formData.append('goal_pace_unit', goalPaceUnit);
44504  if (notes) formData.append('notes', notes);
44505  formData.append('is_public', isPublic ? 'true' : 'false');
44506
44507  // Phase 8: Primary Team (Step 4) - from team selection dropdown
44508  const primaryTeamId = document.getElementById('primary-team-id-input')?.value;
44509  if (primaryTeamId) {
44510    formData.append('primary_team_id', primaryTeamId);
44511  }
44512
44513  // Friends (Step 4) - handled by friend inviter component
44514  if (friendInviter && friendInviter.hasSelections()) {
44515    const selectedData = friendInviter.getSelectedData();
44516
44517    // Add invited RaceMob users (by ID)
44518    const friendIds = selectedData.invited_users || [];
44519    friendIds.forEach(id => formData.append('invitation[invited_users][]', id));
44520
44521    // Add invited emails (non-RaceMob friends)
44522    const emails = selectedData.invited_emails || [];
44523    emails.forEach(email => formData.append('invitation[invited_emails][]', email));
44524
44525    // Add personal invitation note (must be invitation_note, not note)
44526    if (selectedData.invitation_note) {
44527      formData.append('invitation[invitation_note]', selectedData.invitation_note);
44528    }
44529  }
44530
44531  // Submit via AJAX (PATCH to update existing registration)
44532  fetch(`/races/${window.raceId}/register`, {
44533    method: 'PATCH',
44534    body: formData,
44535    headers: {
44536      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || '',
44537      'X-Requested-With': 'XMLHttpRequest'
44538    },
44539    credentials: 'same-origin'
44540  })
44541  .then(response => {
44542    if (!response.ok) {
44543      throw new Error(`HTTP error! status: ${response.status}`);
44544    }
44545    return response.json();
44546  })
44547  .then(data => {
44548    if (data.success) {
44549      // Update button state
44550      updateButtonToGoingState();
44551
44552      // Move to success step
44553      currentStep = 5;
44554      updateStepDisplay();
44555      updateProgressIndicator();
44556      updateButtons();
44557
44558      // Generate registration summary for confirmation step
44559      generateRegistrationSummary();
44560    } else {
44561      showRegistrationError(data.error || 'Failed to update registration');
44562    }
44563  })
44564  .catch(error => {
44565    console.error('Error updating registration:', error);
44566    showRegistrationError('Failed to update registration. Please try again.');
44567  });
44568}
44569
44570// ============================================================================
44571// INLINE AUTHENTICATION FUNCTIONS
44572// ============================================================================
44573
44574/**
44575 * Show the inline auth step within the modal
44576 */
44577// Removed showInlineAuthStep - now using numbered step system
44578// Auth step is activated by setting authStepActive=true and currentStep=3
44579
44580/**
44581 * Switch between signup and signin tabs
44582 */
44583// Setup keyboard handlers for auth step inputs
44584function setupAuthStepKeyboardHandlers() {
44585  const signupEmailInput = document.getElementById('auth-email-signup');
44586  const signinEmailInput = document.getElementById('auth-email-signin');
44587  const signinPasswordInput = document.getElementById('auth-password-signin');
44588
44589  // Remove existing listeners if any
44590  if (signupEmailInput) {
44591    signupEmailInput.removeEventListener('keypress', handleSignupEnter);
44592    signupEmailInput.addEventListener('keypress', handleSignupEnter);
44593    // Clear errors when user starts typing
44594    signupEmailInput.addEventListener('input', clearAuthStepError);
44595  }
44596
44597  if (signinPasswordInput) {
44598    signinPasswordInput.removeEventListener('keypress', handleSigninEnter);
44599    signinPasswordInput.addEventListener('keypress', handleSigninEnter);
44600    signinPasswordInput.addEventListener('input', clearAuthStepError);
44601  }
44602
44603  // Also add to email input for signin
44604  if (signinEmailInput) {
44605    signinEmailInput.removeEventListener('keypress', handleSigninEnter);
44606    signinEmailInput.addEventListener('keypress', handleSigninEnter);
44607    signinEmailInput.addEventListener('input', clearAuthStepError);
44608  }
44609}
44610
44611function handleSignupEnter(e) {
44612  if (e.key === 'Enter') {
44613    e.preventDefault();
44614    handleInlineSignup();
44615  }
44616}
44617
44618function handleSigninEnter(e) {
44619  if (e.key === 'Enter') {
44620    e.preventDefault();
44621    handleInlineSignin();
44622  }
44623}
44624
44625window.switchAuthTab = function(tab) {
44626  const signupTab = document.querySelector('.auth-tab[data-tab="signup"]');
44627  const signinTab = document.querySelector('.auth-tab[data-tab="signin"]');
44628  const signupForm = document.querySelector('.signup-form');
44629  const signinForm = document.querySelector('.signin-form');
44630  const titleElement = document.getElementById('auth-step-title');
44631  const subtitleElement = document.getElementById('auth-step-subtitle');
44632
44633  // Clear any error messages when switching tabs
44634  clearAuthStepError();
44635
44636  // Reset inline forgot-password form when switching tabs
44637  hideRegModalForgotPassword();
44638
44639  if (tab === 'signup') {
44640    signupTab.classList.add('active');
44641    signinTab.classList.remove('active');
44642    signupForm.style.display = 'block';
44643    signinForm.style.display = 'none';
44644    titleElement.textContent = 'Save This Race';
44645    subtitleElement.textContent = 'Enter your email to track your upcoming races';
44646  } else {
44647    signinTab.classList.add('active');
44648    signupTab.classList.remove('active');
44649    signinForm.style.display = 'block';
44650    signupForm.style.display = 'none';
44651    titleElement.textContent = 'Welcome Back!';
44652    subtitleElement.textContent = 'Sign in to continue';
44653  }
44654};
44655
44656/**
44657 * Handle inline signup submission (email only)
44658 * Uses the same endpoint as auth_modal for consistency
44659 */
44660window.handleInlineSignup = async function() {
44661  const email = document.getElementById('auth-email-signup').value;
44662
44663  if (!email) {
44664    showRegistrationError('Please enter your email');
44665    return;
44666  }
44667
44668  // Validate email format using shared utility
44669  if (!AuthUtils.validateEmail(email)) {
44670    showRegistrationError('Please enter a valid email address');
44671    return;
44672  }
44673
44674  try {
44675    // Signup-source telemetry: legacy in-modal signup is the same
44676    // registration-wizard funnel as the rebuilt page's auth step.
44677    if (window.RaceMobSignup) {
44678      RaceMobSignup.mark('race_registration_wizard', window.raceId ? { race_id: window.raceId } : {});
44679    }
44680
44681    // Use shared AuthUtils for signup logic
44682    const data = await AuthUtils.quickSignup(email, { source: 'enhanced_registration' });
44683
44684    if (data.success) {
44685      // User is now logged in! Backend sent verification email
44686      console.log('Quick signup successful, user auto-logged in');
44687
44688      // Update auth state immediately so modal can continue
44689      document.body.dataset.userLoggedIn = 'true';
44690
44691      // Update verification status (new users are NOT verified)
44692      const modal = document.getElementById('enhanced-registration-modal');
44693      if (modal) {
44694        modal.setAttribute('data-user-verified', 'false');
44695      }
44696
44697      // Store user email for verification overlay
44698      if (data.user && data.user.email) {
44699        const emailHolder = document.querySelector('[data-user-email]');
44700        if (emailHolder) {
44701          emailHolder.setAttribute('data-user-email', data.user.email);
44702        } else {
44703          // Create data holder if it doesn't exist
44704          const holder = document.createElement('div');
44705          holder.setAttribute('data-user-email', data.user.email);
44706          holder.style.display = 'none';
44707          document.body.appendChild(holder);
44708        }
44709      }
44710
44711      // Update navigation in background using shared utility
44712      await AuthUtils.updatePageAuthState(data.user);
44713
44714      // Mark that we need to refresh on modal close
44715      window.needsPageRefreshOnModalClose = true;
44716
44717      // Wait for cookies to sync before calling registration endpoint using shared utility
44718      AuthUtils.waitForCookieSync(() => {
44719        submitInitialRegistration();
44720      });
44721    } else if (data.email_exists) {
44722      // Email already registered - switch to signin tab
44723      showRegistrationError('This email is already registered. Please sign in instead.');
44724      setTimeout(() => {
44725        switchAuthTab('signin');
44726        // Pre-fill email
44727        document.getElementById('auth-email-signin').value = email;
44728
44729        // Highlight OAuth provider if user previously used it
44730        if (data.oauth_provider) {
44731          setTimeout(() => {
44732            highlightOAuthProvider(data.oauth_provider);
44733          }, 150);
44734        }
44735      }, 1500);
44736    } else {
44737      showRegistrationError(data.error || 'Signup failed. Please try again.');
44738    }
44739  } catch (error) {
44740    console.error('Error during signup:', error);
44741    showRegistrationError('Signup failed. Please try again.');
44742  }
44743};
44744
44745/**
44746 * Handle inline signin submission
44747 *
44748 * AuthUtils.signIn returns `{ success: false, error, oauth_provider?, email? }`
44749 * for wrong-credentials and OAuth-only cases (it does NOT throw). The previous
44750 * implementation only caught thrown errors, so a failed sign-in fell through
44751 * and the wizard kept advancing as if the user had logged in — producing the
44752 * "Invalid password not hooked up" symptom on the race registration auth step.
44753 */
44754window.handleInlineSignin = async function() {
44755  const email = document.getElementById('auth-email-signin').value;
44756  const password = document.getElementById('auth-password-signin').value;
44757
44758  if (!email || !password) {
44759    showAuthStepError('Please fill in email and password');
44760    return;
44761  }
44762
44763  // Clear any prior error before submitting
44764  clearAuthStepError();
44765
44766  let result;
44767  try {
44768    result = await AuthUtils.signIn(email, password);
44769  } catch (error) {
44770    console.error('Error during signin:', error);
44771    showAuthStepError(error.message || 'Sign in failed. Please check your credentials.');
44772    return;
44773  }
44774
44775  // AuthUtils.signIn returns a structured failure object instead of throwing
44776  // when the server responds with a JSON envelope (legacy `{html: ...}` shape
44777  // from try_login_again, or an OAuth-only-user hint).
44778  if (!result || result.success === false) {
44779    var message = (result && result.error) || 'Invalid email or password';
44780    if (result && result.oauth_provider) {
44781      var providerName = (AuthUtils.getProviderName && AuthUtils.getProviderName(result.oauth_provider)) || result.oauth_provider;
44782      message = 'Invalid password. Hint: you previously signed in with ' + providerName + '.';
44783    }
44784    showAuthStepError(message);
44785    return;
44786  }
44787
44788  // Login succeeded — update auth state so the wizard can continue.
44789  document.body.dataset.userLoggedIn = 'true';
44790
44791  const modal = document.getElementById('enhanced-registration-modal');
44792  if (modal && result.user) {
44793    // Assume verified for returning users (can be enhanced later)
44794    modal.setAttribute('data-user-verified', 'true');
44795  }
44796
44797  await AuthUtils.updatePageAuthState(result.user);
44798
44799  window.needsPageRefreshOnModalClose = true;
44800
44801  AuthUtils.waitForCookieSync(() => {
44802    submitInitialRegistration();
44803  });
44804};
44805
44806/**
44807 * Toggle password visibility
44808 */
44809window.togglePasswordVisibility = function(inputId) {
44810  const input = document.getElementById(inputId);
44811  const button = input.parentElement.querySelector('.password-toggle');
44812  const eyeOpen = button.querySelector('.eye-open');
44813  const eyeClosed = button.querySelector('.eye-closed');
44814
44815  if (input.type === 'password') {
44816    input.type = 'text';
44817    eyeOpen.style.display = 'none';
44818    eyeClosed.style.display = 'block';
44819  } else {
44820    input.type = 'password';
44821    eyeOpen.style.display = 'block';
44822    eyeClosed.style.display = 'none';
44823  }
44824};
44825
44826/**
44827 * Inline forgot-password form in registration modal sign-in tab
44828 */
44829window.showRegModalForgotPassword = function() {
44830  var emailGroup = document.getElementById('reg-modal-email-group');
44831  var passwordGroup = document.getElementById('reg-modal-password-group');
44832  var forgotForm = document.getElementById('reg-modal-forgot-password');
44833  var signinForm = document.querySelector('.signin-form');
44834  var oauthBtns = signinForm ? signinForm.querySelector('.oauth-buttons') : null;
44835  var oauthDivider = signinForm ? signinForm.querySelector('.oauth-divider') : null;
44836  if (emailGroup) emailGroup.style.display = 'none';
44837  if (passwordGroup) passwordGroup.style.display = 'none';
44838  if (oauthBtns) oauthBtns.style.display = 'none';
44839  if (oauthDivider) oauthDivider.style.display = 'none';
44840  if (forgotForm) forgotForm.style.display = 'block';
44841  // Pre-fill email from sign-in field
44842  var signinEmail = document.getElementById('auth-email-signin');
44843  var resetEmail = document.getElementById('reg-modal-reset-email');
44844  if (signinEmail && resetEmail && signinEmail.value) {
44845    resetEmail.value = signinEmail.value;
44846  }
44847};
44848
44849window.hideRegModalForgotPassword = function() {
44850  var emailGroup = document.getElementById('reg-modal-email-group');
44851  var passwordGroup = document.getElementById('reg-modal-password-group');
44852  var forgotForm = document.getElementById('reg-modal-forgot-password');
44853  var message = document.getElementById('reg-modal-forgot-message');
44854  var signinForm = document.querySelector('.signin-form');
44855  var oauthBtns = signinForm ? signinForm.querySelector('.oauth-buttons') : null;
44856  var oauthDivider = signinForm ? signinForm.querySelector('.oauth-divider') : null;
44857  if (emailGroup) emailGroup.style.display = 'block';
44858  if (passwordGroup) passwordGroup.style.display = 'block';
44859  if (oauthBtns) oauthBtns.style.display = '';
44860  if (oauthDivider) oauthDivider.style.display = '';
44861  if (forgotForm) forgotForm.style.display = 'none';
44862  if (message) message.style.display = 'none';
44863};
44864
44865window.submitRegModalForgotPassword = function() {
44866  var emailInput = document.getElementById('reg-modal-reset-email');
44867  var message = document.getElementById('reg-modal-forgot-message');
44868  var submitBtn = document.getElementById('reg-modal-forgot-submit');
44869  var email = emailInput ? emailInput.value.trim() : '';
44870
44871  if (!email) {
44872    message.style.display = 'block';
44873    message.style.background = '#FEF2F2';
44874    message.style.color = '#DC2626';
44875    message.textContent = 'Please enter your email address.';
44876    return;
44877  }
44878
44879  submitBtn.disabled = true;
44880  submitBtn.textContent = 'Sending...';
44881
44882  var csrfToken = document.querySelector('[name="csrf-token"]');
44883  fetch('/passwords', {
44884    method: 'POST',
44885    headers: {
44886      'Content-Type': 'application/json',
44887      'Accept': 'application/json',
44888      'X-Requested-With': 'XMLHttpRequest',
44889      'X-CSRF-Token': csrfToken ? csrfToken.content : ''
44890    },
44891    credentials: 'same-origin',
44892    body: JSON.stringify({ login: email })
44893  })
44894  .then(function(response) {
44895    if (!response.ok) throw new Error('Request failed');
44896    message.style.display = 'block';
44897    message.style.background = '#F0FDF4';
44898    message.style.color = '#16A34A';
44899    message.textContent = 'If an account exists with that email, we\'ve sent reset instructions.';
44900    submitBtn.textContent = 'Sent!';
44901  })
44902  .catch(function() {
44903    message.style.display = 'block';
44904    message.style.background = '#FEF2F2';
44905    message.style.color = '#DC2626';
44906    message.textContent = 'Something went wrong. Please try again.';
44907    submitBtn.disabled = false;
44908    submitBtn.textContent = 'Send Reset Link';
44909  });
44910};
44911
44912/**
44913 * Handle OAuth signup/signin using POST form (matches auth_modal pattern)
44914 */
44915window.handleOAuthSignup = function(provider) {
44916  submitOAuthForm(provider);
44917};
44918
44919window.handleOAuthSignin = function(provider) {
44920  submitOAuthForm(provider);
44921};
44922
44923/**
44924 * Submit OAuth form with CSRF token and store intended action
44925 * This matches the auth_modal.js implementation
44926 */
44927function submitOAuthForm(provider) {
44928  // Map friendly names to OmniAuth provider names
44929  const providerMap = {
44930    'google': 'google_oauth2',
44931    'facebook': 'facebook'
44932  };
44933
44934  const authProvider = providerMap[provider] || provider;
44935
44936  // Create hidden form for POST submission
44937  const form = document.createElement('form');
44938  form.method = 'POST';
44939  form.action = `/auth/${authProvider}`;
44940  form.style.display = 'none';
44941
44942  // Add CSRF token
44943  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
44944  if (csrfToken) {
44945    const csrfInput = document.createElement('input');
44946    csrfInput.type = 'hidden';
44947    csrfInput.name = 'authenticity_token';
44948    csrfInput.value = csrfToken;
44949    form.appendChild(csrfInput);
44950  }
44951
44952  // Store registration intent in sessionStorage for post-OAuth resume
44953  // Get selected distance IDs before OAuth redirect
44954  const distanceIds = [];
44955  document.querySelectorAll('input[name="distance_ids[]"]:checked').forEach(checkbox => {
44956    distanceIds.push(checkbox.value);
44957  });
44958
44959  sessionStorage.setItem('racemob_intended_action', JSON.stringify({
44960    action: 'register',
44961    data: {
44962      raceId: window.raceId,
44963      participationType: selectedParticipationType,
44964      distanceIds: distanceIds
44965    }
44966  }));
44967
44968  // Submit form to trigger OAuth flow
44969  document.body.appendChild(form);
44970  form.submit();
44971}
44972
44973/**
44974 * Highlight OAuth provider button with pulsing animation
44975 * Matches auth_modal.js implementation
44976 */
44977function highlightOAuthProvider(provider) {
44978  const providerClass = provider === 'google_oauth2' ? 'google-btn' : 'facebook-btn';
44979  const button = document.querySelector(`.auth-step-content .${providerClass}`);
44980
44981  if (button) {
44982    // Add pulsing animation
44983    button.style.animation = 'pulse 1.5s ease-in-out infinite';
44984    button.style.boxShadow = '0 0 0 3px rgba(66, 133, 244, 0.3)';
44985
44986    // Add CSS for pulse animation if it doesn't exist
44987    if (!document.getElementById('oauth-highlight-styles')) {
44988      const style = document.createElement('style');
44989      style.id = 'oauth-highlight-styles';
44990      style.textContent = `
44991        @keyframes pulse {
44992          0%, 100% { transform: scale(1); }
44993          50% { transform: scale(1.05); }
44994        }
44995      `;
44996      document.head.appendChild(style);
44997    }
44998  }
44999}
45000
45001/**
45002 * Open registration modal for races page (data already on page)
45003 * Sets window.raceId from page data before opening modal
45004 */
45005window.openRaceRegistrationModal = function(raceId) {
45006  // Set global raceId
45007  window.raceId = raceId;
45008
45009  // Open the modal directly (race data already on page)
45010  openEnhancedRegistrationModal();
45011};
45012
45013// Expose to window for global access
45014window.openEnhancedRegistrationModal = openEnhancedRegistrationModal;
45015window.closeEnhancedRegistrationModal = closeEnhancedRegistrationModal;
45016
45017// Phase 8: Fetch and render teams for registration modal (compact dropdown design)
45018async function fetchAndRenderTeams() {
45019  const modal = document.getElementById('enhanced-registration-modal');
45020  if (!modal || !window.raceId) {
45021    return;
45022  }
45023
45024  try {
45025    const response = await fetch(`/races/${window.raceId}/user_teams`);
45026    const data = await response.json();
45027
45028    const section = document.getElementById('team-selection-section');
45029    const hasTeamsEl = document.getElementById('team-select-has-teams');
45030    const noTeamsEl = document.getElementById('team-select-no-teams');
45031    const optionsContainer = document.getElementById('team-select-options');
45032    const trigger = document.getElementById('team-select-trigger');
45033    const valueDisplay = document.getElementById('team-select-value');
45034
45035    // Always show the section
45036    section.style.display = 'block';
45037
45038    if (data.teams && data.teams.length > 0) {
45039      // User has teams - show dropdown
45040      hasTeamsEl.style.display = 'flex';
45041      noTeamsEl.style.display = 'none';
45042
45043      // Store teams data for later use
45044      window.teamsData = data.teams;
45045
45046      // Build dropdown options
45047      const checkIcon = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"></polyline></svg>';
45048
45049      // Find global primary team or fall back to first team
45050      const globalPrimaryTeam = data.teams.find(t => t.is_global_primary);
45051      const defaultTeam = globalPrimaryTeam || data.teams[0];
45052      const defaultTeamId = defaultTeam.id;
45053
45054      optionsContainer.innerHTML = data.teams.map((team) => {
45055        const isDefault = team.id === defaultTeamId;
45056
45057        // Different meta text based on whether team is at race
45058        let metaText, metaClass = '';
45059        if (team.at_race) {
45060          metaText = team.teammates_count > 0
45061            ? `${team.teammates_count} teammate${team.teammates_count !== 1 ? 's' : ''} going`
45062            : 'Be the first from your team';
45063        } else {
45064          metaText = 'Add your team to this race';
45065          metaClass = 'new-team';
45066        }
45067
45068        // Handle image with proper fallback - encode URL to handle special chars like parentheses
45069        const safeLogoUrl = team.logo_url ? encodeURI(team.logo_url) : null;
45070        const safeName = escapeHtml(team.name);
45071        const safeInitials = escapeHtml(team.name.substring(0, 2).toUpperCase());
45072        const avatarHtml = safeLogoUrl
45073          ? `<img src="${safeLogoUrl}" alt="${safeName}" class="team-option-avatar" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
45074             <div class="team-option-avatar-placeholder" style="display:none;">${safeInitials}</div>`
45075          : `<div class="team-option-avatar-placeholder">${safeInitials}</div>`;
45076
45077        // Add "Primary" badge for global primary team
45078        const primaryBadge = team.is_global_primary ? '<span class="team-option-badge">Primary</span>' : '';
45079
45080        return `
45081          <div class="team-select-option ${isDefault ? 'selected' : ''}" data-team-id="${team.id}" data-team-name="${safeName}" data-logo-url="${safeLogoUrl || ''}">
45082            ${avatarHtml}
45083            <div class="team-option-details">
45084              <div class="team-option-name">${safeName}${primaryBadge}</div>
45085              <div class="team-option-meta ${metaClass}">${metaText}</div>
45086            </div>
45087            <div class="team-option-check">${checkIcon}</div>
45088          </div>
45089        `;
45090      }).join('') + `
45091        <div class="team-select-option independent" data-team-id="" data-team-name="Racing independently" data-logo-url="">
45092          <div class="team-option-avatar-placeholder">
45093            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
45094              <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
45095              <circle cx="12" cy="7" r="4"></circle>
45096            </svg>
45097          </div>
45098          <div class="team-option-details">
45099            <div class="team-option-name">Racing independently</div>
45100            <div class="team-option-meta">No team affiliation</div>
45101          </div>
45102          <div class="team-option-check">${checkIcon}</div>
45103        </div>
45104      `;
45105
45106      // Set initial selection (global primary team or first team)
45107      document.getElementById('primary-team-id-input').value = defaultTeam.id;
45108      window.selectedPrimaryTeamId = defaultTeam.id;
45109      updateTeamTriggerDisplay(defaultTeam.name, defaultTeam.logo_url);
45110      updateSummaryTeam(defaultTeam.name);
45111
45112      // Initialize dropdown event listeners (only once)
45113      initTeamDropdownListeners();
45114
45115    } else {
45116      // User has no teams - show CTA to join/create
45117      hasTeamsEl.style.display = 'none';
45118      noTeamsEl.style.display = 'flex';
45119    }
45120  } catch (error) {
45121    console.error('Failed to fetch teams:', error);
45122    // On error, hide the section
45123    section.style.display = 'none';
45124  }
45125}
45126
45127function closeTeamDropdown() {
45128  const dropdown = document.getElementById('team-select-dropdown');
45129  if (dropdown) dropdown.classList.remove('open');
45130}
45131
45132// Track modal element to detect if DOM was replaced (e.g., by Turbolinks)
45133// Use var to allow redeclaration on Turbolinks page loads
45134var teamDropdownModalElement = window.teamDropdownModalElement || null;
45135var teamDropdownDocListenerAdded = window.teamDropdownDocListenerAdded || false;
45136
45137function initTeamDropdownListeners() {
45138  // Use event delegation on the modal for reliability
45139  const modal = document.getElementById('enhanced-registration-modal');
45140  if (!modal) return;
45141
45142  // Skip if listeners already attached to this exact modal element
45143  if (teamDropdownModalElement === modal) return;
45144  teamDropdownModalElement = modal;
45145
45146  // Toggle dropdown when clicking trigger
45147  modal.addEventListener('click', (e) => {
45148    const trigger = e.target.closest('#team-select-trigger');
45149    if (trigger) {
45150      e.preventDefault();
45151      e.stopPropagation();
45152      const dropdown = document.getElementById('team-select-dropdown');
45153      const isOpen = dropdown && dropdown.classList.contains('open');
45154      if (isOpen) {
45155        closeTeamDropdown();
45156      } else {
45157        if (dropdown) dropdown.classList.add('open');
45158      }
45159      return;
45160    }
45161
45162    // Handle option selection
45163    const option = e.target.closest('.team-select-option');
45164    if (option) {
45165      const teamId = option.dataset.teamId;
45166      const teamName = option.dataset.teamName;
45167      const logoUrl = option.dataset.logoUrl;
45168
45169      // Update selection state
45170      const optionsContainer = document.getElementById('team-select-options');
45171      if (optionsContainer) {
45172        optionsContainer.querySelectorAll('.team-select-option').forEach(o => o.classList.remove('selected'));
45173      }
45174      option.classList.add('selected');
45175
45176      // Update hidden input and global state
45177      const hiddenInput = document.getElementById('primary-team-id-input');
45178      if (hiddenInput) hiddenInput.value = teamId;
45179      window.selectedPrimaryTeamId = teamId || null;
45180
45181      // Update trigger display
45182      updateTeamTriggerDisplay(teamName, logoUrl);
45183      updateSummaryTeam(teamName);
45184
45185      // Close dropdown
45186      closeTeamDropdown();
45187      return;
45188    }
45189
45190    // Close dropdown when clicking outside of it (but inside modal)
45191    if (!e.target.closest('.team-select-dropdown')) {
45192      closeTeamDropdown();
45193    }
45194  });
45195
45196  // Also close on document click for clicks outside modal (only add once)
45197  if (!teamDropdownDocListenerAdded) {
45198    teamDropdownDocListenerAdded = true;
45199    document.addEventListener('click', (e) => {
45200      if (!e.target.closest('.team-select-dropdown') && !e.target.closest('#enhanced-registration-modal')) {
45201        closeTeamDropdown();
45202      }
45203    });
45204  }
45205}
45206
45207function updateTeamTriggerDisplay(teamName, logoUrl) {
45208  const valueDisplay = document.getElementById('team-select-value');
45209  if (!valueDisplay) return;
45210
45211  valueDisplay.classList.remove('placeholder');
45212
45213  if (teamName === 'Racing independently') {
45214    valueDisplay.innerHTML = `
45215      <div class="team-select-value-avatar-placeholder" style="background: linear-gradient(135deg, #9CA3AF 0%, #6B7280 100%);">
45216        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
45217          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
45218          <circle cx="12" cy="7" r="4"></circle>
45219        </svg>
45220      </div>
45221      <span>Racing independently</span>
45222    `;
45223  } else {
45224    // Encode URL to handle special chars like parentheses
45225    const safeLogoUrl = logoUrl ? encodeURI(logoUrl) : null;
45226    const avatarHtml = safeLogoUrl
45227      ? `<img src="${safeLogoUrl}" class="team-select-value-avatar" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
45228         <div class="team-select-value-avatar-placeholder" style="display:none;">${teamName.substring(0, 2).toUpperCase()}</div>`
45229      : `<div class="team-select-value-avatar-placeholder">${teamName.substring(0, 2).toUpperCase()}</div>`;
45230
45231    valueDisplay.innerHTML = `${avatarHtml}<span>${teamName}</span>`;
45232  }
45233}
45234;
45235/**
45236 * Shared advertising functionality
45237 * Handles deferred loading of AdSense and other advertising scripts
45238 */
45239
45240window.Advertising = {
45241  /**
45242   * Load AdSense script when browser is idle for better performance
45243   */
45244  loadAdSenseDeferred: function() {
45245    if (window.requestIdleCallback) {
45246      requestIdleCallback(function() {
45247        var script = document.createElement('script');
45248        script.async = true;
45249        script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4920802898007759';
45250        script.crossOrigin = 'anonymous';
45251        document.head.appendChild(script);
45252      });
45253    } else {
45254      // Fallback for browsers without requestIdleCallback
45255      window.addEventListener('load', function() {
45256        var script = document.createElement('script');
45257        script.async = true;
45258        script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4920802898007759';
45259        script.crossOrigin = 'anonymous';
45260        document.head.appendChild(script);
45261      });
45262    }
45263  }
45264};
45265//
45266// Searches Fast Bundle - Optimized JavaScript for race search/listing pages  
45267// Based on races_modern_bundle.js approach
45268// Includes essential dependencies for search functionality
45269//
45270
45271// Core dependencies (jQuery, rails-ujs, modal_utilities, ui_helpers all in base.js)
45272
45273
45274
45275// Essential jQuery plugins for search UI
45276
45277
45278
45279// Core site functionality
45280
45281
45282
45283
45284// Search page specific functionality
45285// (search_core / search_utilities / unified_filter_handlers / searches_fast dropped —
45286// search_rebuild.js is self-contained; see docs/superpowers/plans/
45287// 2026-07-18-searches-fast-bundle-extraction.md)
45288
45289
45290// Race interaction modules (for save/target buttons)
45291
45292
45293// (The search page's suggest-a-race modal is searches_fast/_suggest_race_modal on the rm-modal
45294// chassis, driven by search_rebuild.js. The legacy modern_race_autocomplete.js and
45295// suggest_race_modal.js it replaced were deleted in #677.)
45296
45297// Enhanced registration modal (for search page registration)
45298
45299
45300
45301// Advertising functionality
45302
45303
45304// Conservative loading complete - optimized for search pages
45305// (auth_modal and navigation already in base.js)
45306;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.